AVRSack/AVRsack/ASFileTree.swift

428 lines
14 KiB
Swift
Raw Normal View History

2014-11-16 18:39:32 +00:00
//
// ASFileTree.swift
// AVRsack
//
// Created by Matthias Neeracher on 11/16/14.
// Copyright (c) 2014 Aere Perennius. All rights reserved.
//
import Foundation
enum ASFileType : String {
case Unknown = ""
case Header = "source.h"
case CFile = "source.c"
case Arduino = "source.ino"
case CppFile = "source.c++"
case AsmFile = "source.asm"
case Markdown = "doc.md"
2016-11-13 12:00:29 +00:00
static func guessForURL(url: URL) -> ASFileType {
switch url.pathExtension.lowercased() {
2014-11-16 18:39:32 +00:00
case "hpp", "hh", "h":
return .Header
case "c":
return .CFile
case "ino":
return .Arduino
case "cpp", "c++", "cxx", "cc":
return .CppFile
case "s":
return .AsmFile
case "md":
return .Markdown
default:
return .Unknown
}
}
var aceMode : ACEMode {
switch self {
case .Header,.CFile,.CppFile,.Arduino:
2015-03-14 20:15:27 +00:00
return .CPP
2014-11-16 18:39:32 +00:00
case .Markdown:
2016-11-13 11:03:51 +00:00
return .markdown
2014-11-16 18:39:32 +00:00
default:
2016-11-13 11:03:51 +00:00
return .text
2014-11-16 18:39:32 +00:00
}
}
}
2014-11-17 01:29:55 +00:00
private let kTypeKey = "Type"
private let kNodeTypeProject = "Project"
private let kNodeTypeGroup = "Group"
private let kNodeTypeFile = "File"
private let kNameKey = "Name"
class ASFileNode : Equatable {
2015-03-16 04:39:09 +00:00
var name : String
init(name: String) {
self.name = name
}
2014-11-16 18:39:32 +00:00
func nodeName() -> String {
return ""
}
2016-11-13 12:00:29 +00:00
func apply(closure:(ASFileNode)->()) {
closure(self)
}
2016-11-13 12:00:29 +00:00
func propertyList(rootPath: String) -> Dictionary<String, AnyObject> {
return [:]
2014-11-17 01:29:55 +00:00
}
2016-11-13 12:00:29 +00:00
class func readPropertyList(prop: Dictionary<String, AnyObject>, rootURL: URL) -> ASFileNode {
2015-02-14 16:43:20 +00:00
switch prop[kTypeKey] as! String {
2014-11-17 01:29:55 +00:00
case kNodeTypeProject:
return ASProject(prop, withRootURL:rootURL)
case kNodeTypeGroup:
return ASFileGroup(prop, withRootURL:rootURL)
case kNodeTypeFile:
return ASFileItem(prop, withRootURL:rootURL)
default:
assertionFailure("Undefined item type in file hierarchy")
2015-04-04 21:43:47 +00:00
abort()
2014-11-17 01:29:55 +00:00
}
}
2016-11-13 12:00:29 +00:00
2015-02-14 16:43:20 +00:00
func paths(rootPath: String) -> [String] {
return [String]()
2014-11-28 13:18:53 +00:00
}
2016-11-13 12:00:29 +00:00
2015-01-11 01:53:50 +00:00
func exists() -> Bool {
return true
}
2016-11-13 12:00:29 +00:00
func modDate() -> Date? {
2015-03-16 04:39:09 +00:00
return nil;
}
2016-11-13 12:00:29 +00:00
2015-03-16 04:39:09 +00:00
func revision() -> String? {
return nil;
}
2014-11-16 18:39:32 +00:00
}
func ==(a: ASFileNode, b: ASFileNode) -> Bool {
return a === b
}
2014-12-01 02:34:53 +00:00
class ASLogNode : ASFileNode {
var path : String
init(name: String, path: String) {
self.path = path
2015-03-16 04:39:09 +00:00
super.init(name: name)
2014-12-01 02:34:53 +00:00
}
2016-11-13 12:00:29 +00:00
2014-12-01 02:34:53 +00:00
override func nodeName() -> String {
return "📜 "+name
}
}
2014-11-16 18:39:32 +00:00
class ASFileGroup : ASFileNode {
var children : [ASFileNode]
var expanded : Bool
2014-11-17 01:29:55 +00:00
private let kChildrenKey = "Children"
private let kExpandedKey = "Expanded"
private var kNodeType : String { return kNodeTypeGroup }
2015-03-16 04:39:09 +00:00
override init(name: String = "") {
2014-11-16 18:39:32 +00:00
self.children = []
self.expanded = true
2015-03-16 04:39:09 +00:00
super.init(name: name)
2014-11-16 18:39:32 +00:00
}
2016-11-13 12:00:29 +00:00
init(_ prop: Dictionary<String, AnyObject>, withRootURL rootURL: URL) {
2015-02-14 16:43:20 +00:00
expanded = prop[kExpandedKey] as! Bool
2014-11-17 01:29:55 +00:00
children = []
2016-11-13 12:00:29 +00:00
for child in (prop[kChildrenKey] as! [Dictionary<String, AnyObject>]) {
2016-11-13 11:03:51 +00:00
children.append(ASFileNode.readPropertyList(prop: child, rootURL: rootURL))
2014-11-17 01:29:55 +00:00
}
2015-03-16 04:39:09 +00:00
super.init(name: prop[kNameKey] as! String)
2014-11-16 18:39:32 +00:00
}
2016-11-13 12:00:29 +00:00
2014-11-16 18:39:32 +00:00
override func nodeName() -> String {
return (expanded ? "📂" : "📁")+" "+name
}
2016-11-13 12:00:29 +00:00
override func apply(closure: (ASFileNode) -> ()) {
2016-11-13 11:03:51 +00:00
super.apply(closure: closure)
for child in children {
2016-11-13 11:03:51 +00:00
child.apply(closure: closure)
}
}
2016-11-13 12:00:29 +00:00
2015-02-14 16:43:20 +00:00
func childrenPropertyList(rootPath: String) -> [AnyObject] {
2016-11-13 11:03:51 +00:00
return children.map() { (node) in node.propertyList(rootPath: rootPath) }
2014-11-17 01:29:55 +00:00
}
2016-11-13 12:00:29 +00:00
override func propertyList(rootPath: String) -> Dictionary<String, AnyObject> {
2014-11-17 01:29:55 +00:00
return [kTypeKey: kNodeType, kNameKey: name, kExpandedKey: expanded,
2016-11-13 12:00:29 +00:00
kChildrenKey: childrenPropertyList(rootPath: rootPath)]
2014-11-17 01:29:55 +00:00
}
2016-11-13 12:00:29 +00:00
2015-02-14 16:43:20 +00:00
override func paths(rootPath: String) -> [String] {
var allPaths = [String]()
2014-11-28 13:18:53 +00:00
for child in children {
2016-11-13 11:03:51 +00:00
allPaths += child.paths(rootPath: rootPath)
2014-11-28 13:18:53 +00:00
}
return allPaths
}
}
class ASProject : ASFileGroup {
2014-11-17 01:29:55 +00:00
override private var kNodeType : String { return kNodeTypeProject }
2015-04-29 03:21:23 +00:00
override init(name: String = "") {
super.init(name: name)
}
2016-11-13 12:00:29 +00:00
override init(_ prop: Dictionary<String, AnyObject>, withRootURL rootURL: URL) {
2015-04-29 03:21:23 +00:00
super.init(prop, withRootURL:rootURL)
2016-11-13 12:00:29 +00:00
name = rootURL.lastPathComponent
2015-04-29 03:21:23 +00:00
}
2016-11-13 12:00:29 +00:00
override func nodeName() -> String {
return "📘 "+name
}
2014-11-16 18:39:32 +00:00
}
class ASFileItem : ASFileNode {
2016-11-13 12:00:29 +00:00
var url : URL
2014-11-16 18:39:32 +00:00
var type : ASFileType
2014-11-17 01:29:55 +00:00
private let kPathKey = "Path"
private let kKindKey = "Kind"
2016-11-13 12:00:29 +00:00
init(url: URL, type: ASFileType) {
2014-11-16 18:39:32 +00:00
self.url = url
self.type = type
2016-11-13 12:00:29 +00:00
super.init(name:url.lastPathComponent)
2014-11-16 18:39:32 +00:00
}
2016-11-13 12:00:29 +00:00
init(_ prop: Dictionary<String, AnyObject>, withRootURL rootURL: URL) {
2015-02-14 16:43:20 +00:00
type = ASFileType(rawValue: prop[kKindKey] as! String)!
2016-11-13 12:00:29 +00:00
let path = prop[kPathKey] as! NSString
if path.isAbsolutePath {
url = URL(fileURLWithPath:path as String).standardizedFileURL
2014-12-22 06:00:05 +00:00
} else {
2016-11-13 12:00:29 +00:00
url = URL(fileURLWithPath: path as String, relativeTo: rootURL).standardizedFileURL
2014-12-22 06:00:05 +00:00
}
2016-11-14 00:37:13 +00:00
var fileExists = false
do {
fileExists = try url.checkResourceIsReachable()
} catch {
fileExists = false
}
if !fileExists {
2015-04-29 03:21:23 +00:00
//
// When projects get moved, .ino files get renamed but that fact is not
// yet reflected in the project file.
//
2016-11-13 12:00:29 +00:00
let urlDir = url.deletingLastPathComponent()
let newName = rootURL.appendingPathExtension(url.pathExtension).lastPathComponent
let altURL = urlDir.appendingPathComponent(newName)
2016-11-14 00:37:13 +00:00
if let altExists = try? altURL.checkResourceIsReachable(), altExists {
2016-11-13 12:00:29 +00:00
url = altURL
2015-04-29 03:21:23 +00:00
}
}
2016-11-13 12:00:29 +00:00
super.init(name:url.lastPathComponent)
2014-11-17 01:29:55 +00:00
}
2016-11-13 12:00:29 +00:00
2014-11-16 18:39:32 +00:00
override func nodeName() -> String {
2015-03-16 04:39:09 +00:00
return "📄 "+name
2014-11-16 18:39:32 +00:00
}
2014-11-17 01:29:55 +00:00
func relativePath(relativeTo: String) -> String {
2016-11-13 12:00:29 +00:00
let path = (url.path as NSString).resolvingSymlinksInPath
let relComp = relativeTo.components(separatedBy: "/") as [String]
let pathComp = path.components(separatedBy: "/") as [String]
2014-11-17 01:29:55 +00:00
let relCount = relComp.count
let pathCount = pathComp.count
var matchComp = 0
while (matchComp < relCount && matchComp < pathCount) {
if pathComp[matchComp] == relComp[matchComp] {
2016-11-13 11:03:51 +00:00
matchComp += 1
2014-11-17 01:29:55 +00:00
} else {
break
}
}
if matchComp==1 {
return path
}
2016-11-13 12:00:29 +00:00
let resComp = Array(repeating: "..", count: relCount-matchComp)+pathComp[matchComp..<pathCount]
return resComp.joined(separator: "/")
2014-11-17 01:29:55 +00:00
}
2016-11-13 12:00:29 +00:00
override func propertyList(rootPath: String) -> Dictionary<String, AnyObject> {
return [kTypeKey: kNodeTypeFile, kKindKey: type.rawValue,
kPathKey: relativePath(relativeTo: rootPath)]
2014-11-17 01:29:55 +00:00
}
2016-11-13 12:00:29 +00:00
2015-02-14 16:43:20 +00:00
override func paths(rootPath: String) -> [String] {
2016-11-13 11:03:51 +00:00
return [relativePath(relativeTo: rootPath)]
2014-11-28 13:18:53 +00:00
}
2016-11-13 12:00:29 +00:00
2015-01-11 01:53:50 +00:00
override func exists() -> Bool {
2016-11-14 00:37:13 +00:00
do {
return try url.checkResourceIsReachable()
} catch {
return false
}
2016-11-13 12:00:29 +00:00
}
override func modDate() -> Date? {
2016-11-14 00:37:13 +00:00
let values = try? url.resourceValues(forKeys: [.contentModificationDateKey])
2016-11-13 12:00:29 +00:00
return values?.contentModificationDate
2015-03-16 04:39:09 +00:00
}
2016-11-13 12:00:29 +00:00
2015-03-16 04:39:09 +00:00
override func revision() -> String? {
2016-11-13 11:03:51 +00:00
let task = Task()
task.launchPath = Bundle.main.path(forResource: "FileRevision", ofType: "")!
let outputPipe = Pipe()
2015-03-16 04:39:09 +00:00
task.standardOutput = outputPipe
2016-11-13 11:03:51 +00:00
task.standardError = FileHandle.nullDevice
2016-11-13 12:00:29 +00:00
task.arguments = [url.path]
2015-03-16 04:39:09 +00:00
task.launch()
2016-11-13 12:00:29 +00:00
return String(data: outputPipe.fileHandleForReading.readDataToEndOfFile(), encoding: String.Encoding.utf8)
2015-03-16 04:39:09 +00:00
}
2014-11-16 18:39:32 +00:00
}
class ASFileTree : NSObject, NSOutlineViewDataSource {
2014-12-01 02:34:53 +00:00
var root = ASProject()
2016-11-13 12:00:29 +00:00
var dir = URL(fileURLWithPath: "/")
2014-12-01 02:34:53 +00:00
var buildLog = ASLogNode(name: "Build Log", path: "build/build.log")
var uploadLog = ASLogNode(name: "Upload Log", path: "build/upload.log")
2014-12-10 14:30:51 +00:00
var disassembly = ASLogNode(name: "Disassembly", path: "build/disasm.log")
var dragged = [ASFileNode]()
2014-11-16 18:39:32 +00:00
2016-11-13 12:00:29 +00:00
func addFileURL(url: URL, omitUnknown: Bool = true) {
2016-11-13 11:03:51 +00:00
let type = ASFileType.guessForURL(url: url)
2014-11-16 18:39:32 +00:00
if !omitUnknown || type != .Unknown {
2016-11-13 12:00:29 +00:00
root.children.append(ASFileItem(url: url.standardizedFileURL, type: type))
2014-11-16 18:39:32 +00:00
}
}
2016-11-13 12:00:29 +00:00
func setProjectURL(url: URL) {
root.name = url.deletingPathExtension().lastPathComponent
dir = url.deletingLastPathComponent().standardizedFileURL
}
func projectPath() -> String {
2016-11-13 12:00:29 +00:00
return (dir.path as NSString).resolvingSymlinksInPath
}
func apply(closure: (ASFileNode) -> ()) {
2016-11-13 11:03:51 +00:00
root.apply(closure: closure)
}
2014-11-17 01:29:55 +00:00
func propertyList() -> AnyObject {
2016-11-13 11:03:51 +00:00
return root.propertyList(rootPath: projectPath())
2014-11-17 01:29:55 +00:00
}
2016-11-13 12:00:29 +00:00
func readPropertyList(prop: Dictionary<String, AnyObject>) {
2016-11-13 11:03:51 +00:00
root = ASFileNode.readPropertyList(prop: prop, rootURL:dir) as! ASProject
2014-11-17 01:29:55 +00:00
}
2015-02-14 16:43:20 +00:00
var paths : [String] {
2016-11-13 11:03:51 +00:00
return root.paths(rootPath: projectPath())
2014-11-28 13:18:53 +00:00
}
2014-11-16 18:39:32 +00:00
2014-11-17 01:29:55 +00:00
// MARK: Outline Data Source
2016-11-13 11:03:51 +00:00
func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: AnyObject?) -> Int {
2014-11-16 18:39:32 +00:00
if item == nil {
2014-12-10 14:30:51 +00:00
return 4
2014-11-16 18:39:32 +00:00
} else {
2015-02-14 16:43:20 +00:00
return (item as! ASFileGroup).children.count
2014-11-16 18:39:32 +00:00
}
}
2016-11-13 11:03:51 +00:00
func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: AnyObject?) -> AnyObject {
if item == nil {
2014-12-01 02:34:53 +00:00
switch index {
case 1:
return buildLog
case 2:
return uploadLog
2014-12-10 14:30:51 +00:00
case 3:
return disassembly
2014-12-01 02:34:53 +00:00
default:
return root
}
} else {
2015-02-14 16:43:20 +00:00
let group = item as! ASFileGroup
return group.children[index]
}
2014-11-16 18:39:32 +00:00
}
2016-11-13 11:03:51 +00:00
func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: AnyObject) -> Bool {
2014-11-16 18:39:32 +00:00
return item is ASFileGroup
}
2016-11-13 11:03:51 +00:00
func outlineView(_ outlineView: NSOutlineView, objectValueFor tableColumn: NSTableColumn?, byItem item: AnyObject?) -> AnyObject? {
2015-02-14 16:43:20 +00:00
return (item as! ASFileNode).nodeName()
2014-11-16 18:39:32 +00:00
}
let kLocalReorderPasteboardType = "ASFilePasteboardType"
2016-11-13 11:03:51 +00:00
func outlineView(_ outlineView: NSOutlineView, writeItems items: [AnyObject], to pasteboard: NSPasteboard) -> Bool {
dragged = items as! [ASFileNode]
pasteboard.declareTypes([kLocalReorderPasteboardType], owner: self)
2016-11-13 12:00:29 +00:00
pasteboard.setData(Data(), forType: kLocalReorderPasteboardType)
return true
}
func itemIsDescendentOfDrag(outlineView: NSOutlineView, item: ASFileNode) -> Bool {
if dragged.contains(item) {
return true
} else if item is ASProject {
return false
} else {
2016-11-13 11:03:51 +00:00
return itemIsDescendentOfDrag(outlineView: outlineView, item: outlineView.parent(forItem: item) as! ASFileNode)
}
}
2016-11-13 11:03:51 +00:00
func outlineView(_ outlineView: NSOutlineView, validateDrop info: NSDraggingInfo, proposedItem item: AnyObject?, proposedChildIndex index: Int) -> NSDragOperation {
if info.draggingPasteboard().availableType(from: [kLocalReorderPasteboardType]) == nil {
return [] // Only allow reordering drags
}
for drag in dragged {
switch (drag) {
case is ASProject, is ASLogNode:
2016-11-13 11:03:51 +00:00
return [] // Don't allow root or log nodes to be dragged
default:
break
}
}
switch (item) {
case is ASProject, is ASFileGroup:
2016-11-13 11:03:51 +00:00
if itemIsDescendentOfDrag(outlineView: outlineView, item: item as! ASFileNode) {
return [] // Don't allow drag on member of dragged items or a descendent thereof
}
default:
2016-11-13 11:03:51 +00:00
return [] // Don't allow drag onto leaf
}
2016-11-13 11:03:51 +00:00
return NSDragOperation.generic
}
2016-11-13 11:03:51 +00:00
func outlineView(_ outlineView: NSOutlineView, acceptDrop info: NSDraggingInfo, item: AnyObject?, childIndex insertAtIndex: Int) -> Bool {
var insertAtIndex = insertAtIndex
let parent : ASFileGroup = (item as? ASFileGroup) ?? root
if insertAtIndex == NSOutlineViewDropOnItemIndex {
insertAtIndex = parent.children.count
}
outlineView.beginUpdates()
for item in dragged {
2016-11-13 11:03:51 +00:00
let origParent = outlineView.parent(forItem: item) as! ASFileGroup
let origIndex = origParent.children.index(of: item)!
origParent.children.remove(at: origIndex)
2016-11-13 12:00:29 +00:00
outlineView.removeItems(at:IndexSet(integer:origIndex), inParent:origParent, withAnimation:[])
if origParent == parent && insertAtIndex > origIndex {
insertAtIndex -= 1
}
2016-11-13 11:03:51 +00:00
parent.children.insert(item, at:insertAtIndex)
2016-11-13 12:00:29 +00:00
outlineView.insertItems(at:IndexSet(integer:insertAtIndex), inParent: parent, withAnimation:NSTableViewAnimationOptions.effectGap)
insertAtIndex += 1
}
outlineView.endUpdates()
2016-11-13 11:03:51 +00:00
(outlineView.delegate as! ASProjDoc).updateChangeCount(NSDocumentChangeType.changeDone)
return true
}
2016-11-13 11:03:51 +00:00
}