From 7d99f7922d34a1dbbd5f30c418d078e98f1d1677 Mon Sep 17 00:00:00 2001 From: marcocarnevali Date: Thu, 17 Mar 2022 16:27:31 +0100 Subject: [PATCH] feat: added code editor --- CodeEdit.xcodeproj/project.pbxproj | 37 +++------ .../xcshareddata/swiftpm/Package.resolved | 34 +++++++++ CodeEdit/ContentView.swift | 4 +- .../CodeEditDocumentController.swift | 4 +- CodeEdit/Documents/CodeFile.swift | 50 ------------ CodeEdit/Documents/WorkspaceDocument.swift | 30 +++++--- CodeEdit/Editor/CodeFileEditor.swift | 22 ------ CodeEdit/Editor/EditorView.swift | 24 ------ CodeEdit/Editor/WorkspaceEditorView.swift | 27 ------- CodeEdit/Info.plist | 2 +- CodeEdit/SideBar/SideBar.swift | 22 +++--- CodeEdit/SideBar/SideBarItem.swift | 22 ++++-- .../Modules/CodeFile/Tests/UnitTests.swift | 33 ++++++++ .../UnitTests/testViewSnapshot.1.png | Bin 0 -> 6125 bytes .../Modules/CodeFile/src/CodeEditorView.swift | 28 +++++++ .../Modules/CodeFile/src/CodeFile.swift | 71 ++++++++++++++++++ .../src/Model/FileItem+Array.swift | 26 +++---- .../WorkspaceClient/src/Model/FileItem.swift | 18 +++-- CodeEditModules/Package.swift | 34 ++++++++- 19 files changed, 281 insertions(+), 207 deletions(-) create mode 100644 CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved delete mode 100644 CodeEdit/Documents/CodeFile.swift delete mode 100644 CodeEdit/Editor/CodeFileEditor.swift delete mode 100644 CodeEdit/Editor/EditorView.swift delete mode 100644 CodeEdit/Editor/WorkspaceEditorView.swift create mode 100644 CodeEditModules/Modules/CodeFile/Tests/UnitTests.swift create mode 100644 CodeEditModules/Modules/CodeFile/Tests/__Snapshots__/UnitTests/testViewSnapshot.1.png create mode 100644 CodeEditModules/Modules/CodeFile/src/CodeEditorView.swift create mode 100644 CodeEditModules/Modules/CodeFile/src/CodeFile.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index adb1dff775..95526a7588 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -7,16 +7,12 @@ objects = { /* Begin PBXBuildFile section */ - 0439FEF527DD104500528317 /* WorkspaceEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0439FEF427DD104500528317 /* WorkspaceEditorView.swift */; }; 043C321427E31FF6006AE443 /* CodeEditDocumentController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */; }; 043C321627E3201F006AE443 /* WorkspaceDocument.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043C321527E3201F006AE443 /* WorkspaceDocument.swift */; }; - 043C321827E32246006AE443 /* CodeFileEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043C321727E32246006AE443 /* CodeFileEditor.swift */; }; 043C321A27E32295006AE443 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 043C321927E32295006AE443 /* MainMenu.xib */; }; 04540D5B27DD08C300E91B77 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F2BF0E27DBB28E0024EAB1 /* SettingsView.swift */; }; 04540D5C27DD08C300E91B77 /* GeneralSettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04F2BF1127DBB3C10024EAB1 /* GeneralSettingsView.swift */; }; 04540D5E27DD08C300E91B77 /* WorkspaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */; }; - 04540D5F27DD08C300E91B77 /* EditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043DF9C127DD045800CA0FC3 /* EditorView.swift */; }; - 04540D6127DD08C300E91B77 /* CodeFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348313FB27DC8C070016D42C /* CodeFile.swift */; }; 04540D6327DD08C300E91B77 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0468438427DC76E200F8E88E /* AppDelegate.swift */; }; 04660F6127E3A68A00477777 /* Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 04660F6027E3A68A00477777 /* Info.plist */; }; 04660F6427E3ACAF00477777 /* Appearances.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04660F6327E3ACAF00477777 /* Appearances.swift */; }; @@ -25,11 +21,12 @@ 287776E927E34BC700D46668 /* TabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776E827E34BC700D46668 /* TabBar.swift */; }; 287776ED27E350D800D46668 /* SideBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776EC27E350D800D46668 /* SideBarItem.swift */; }; 287776EF27E3515300D46668 /* TabBarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776EE27E3515300D46668 /* TabBarItem.swift */; }; - 28B0A19827E385C300B73177 /* SideBarToolbarTop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B0A19727E385C300B73177 /* SideBarToolbarTop.swift */; }; - 28FFE1BF27E3A441001939DB /* SideBarToolbarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FFE1BE27E3A441001939DB /* SideBarToolbarBottom.swift */; }; + 28B0A19827E385C300B73177 /* SideBarToolbarTop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28B0A19727E385C300B73177 /* SideBarToolbarTop.swift */; }; + 28FFE1BF27E3A441001939DB /* SideBarToolbarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = 28FFE1BE27E3A441001939DB /* SideBarToolbarBottom.swift */; }; 345F667527DF6C180069BD69 /* FileTabRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 345F667427DF6C180069BD69 /* FileTabRow.swift */; }; 34EE19BE27E0469C00F152CE /* BlurView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 34EE19BD27E0469C00F152CE /* BlurView.swift */; }; 5C403B8F27E20F8000788241 /* WorkspaceClient in Frameworks */ = {isa = PBXBuildFile; productRef = 5C403B8E27E20F8000788241 /* WorkspaceClient */; }; + 5CF38A5E27E48E6C0096A0F7 /* CodeFile in Frameworks */ = {isa = PBXBuildFile; productRef = 5CF38A5D27E48E6C0096A0F7 /* CodeFile */; }; B658FB3427DA9E1000EA4DBD /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3327DA9E1000EA4DBD /* Assets.xcassets */; }; B658FB3727DA9E1000EA4DBD /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B658FB3627DA9E1000EA4DBD /* Preview Assets.xcassets */; }; D7211D4327E066CE008F2ED7 /* Localized+Ex.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7211D4227E066CE008F2ED7 /* Localized+Ex.swift */; }; @@ -54,12 +51,9 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ - 0439FEF427DD104500528317 /* WorkspaceEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceEditorView.swift; sourceTree = ""; }; 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditDocumentController.swift; sourceTree = ""; }; 043C321527E3201F006AE443 /* WorkspaceDocument.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceDocument.swift; sourceTree = ""; }; - 043C321727E32246006AE443 /* CodeFileEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeFileEditor.swift; sourceTree = ""; }; 043C321927E32295006AE443 /* MainMenu.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = MainMenu.xib; sourceTree = ""; }; - 043DF9C127DD045800CA0FC3 /* EditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorView.swift; sourceTree = ""; }; 04660F6027E3A68A00477777 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 04660F6327E3ACAF00477777 /* Appearances.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appearances.swift; sourceTree = ""; }; 04660F6527E3ACEF00477777 /* ReopenBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReopenBehavior.swift; sourceTree = ""; }; @@ -73,7 +67,6 @@ 28B0A19727E385C300B73177 /* SideBarToolbarTop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBarToolbarTop.swift; sourceTree = ""; }; 28FFE1BE27E3A441001939DB /* SideBarToolbarBottom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SideBarToolbarBottom.swift; sourceTree = ""; }; 345F667427DF6C180069BD69 /* FileTabRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileTabRow.swift; sourceTree = ""; }; - 348313FB27DC8C070016D42C /* CodeFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeFile.swift; sourceTree = ""; }; 34EE19BD27E0469C00F152CE /* BlurView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlurView.swift; sourceTree = ""; }; B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CodeEdit.app; sourceTree = BUILT_PRODUCTS_DIR; }; B658FB3127DA9E0F00EA4DBD /* WorkspaceView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceView.swift; sourceTree = ""; }; @@ -93,6 +86,7 @@ buildActionMask = 2147483647; files = ( 5C403B8F27E20F8000788241 /* WorkspaceClient in Frameworks */, + 5CF38A5E27E48E6C0096A0F7 /* CodeFile in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -113,20 +107,9 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 0439FEF327DD100800528317 /* Editor */ = { - isa = PBXGroup; - children = ( - 043DF9C127DD045800CA0FC3 /* EditorView.swift */, - 0439FEF427DD104500528317 /* WorkspaceEditorView.swift */, - 043C321727E32246006AE443 /* CodeFileEditor.swift */, - ); - path = Editor; - sourceTree = ""; - }; 043C321227E31FE8006AE443 /* Documents */ = { isa = PBXGroup; children = ( - 348313FB27DC8C070016D42C /* CodeFile.swift */, 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */, 043C321527E3201F006AE443 /* WorkspaceDocument.swift */, ); @@ -224,7 +207,6 @@ 287776EA27E350A100D46668 /* SideBar */, 287776EB27E350BA00D46668 /* TabBar */, 345F667327DF6BCC0069BD69 /* Rows */, - 0439FEF327DD100800528317 /* Editor */, 04F2BF1027DBB3AF0024EAB1 /* Settings */, 34EE19BC27E0467F00F152CE /* CustomViews */, D7211D4427E066D4008F2ED7 /* Localization */, @@ -271,6 +253,7 @@ name = CodeEdit; packageProductDependencies = ( 5C403B8E27E20F8000788241 /* WorkspaceClient */, + 5CF38A5D27E48E6C0096A0F7 /* CodeFile */, ); productName = CodeEdit; productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */; @@ -395,7 +378,6 @@ 043C321427E31FF6006AE443 /* CodeEditDocumentController.swift in Sources */, 04660F6427E3ACAF00477777 /* Appearances.swift in Sources */, 04540D5E27DD08C300E91B77 /* WorkspaceView.swift in Sources */, - 04540D5F27DD08C300E91B77 /* EditorView.swift in Sources */, 34EE19BE27E0469C00F152CE /* BlurView.swift in Sources */, D7211D4327E066CE008F2ED7 /* Localized+Ex.swift in Sources */, 287776E927E34BC700D46668 /* TabBar.swift in Sources */, @@ -403,14 +385,11 @@ 287776EF27E3515300D46668 /* TabBarItem.swift in Sources */, 287776E727E3413200D46668 /* SideBar.swift in Sources */, 287776ED27E350D800D46668 /* SideBarItem.swift in Sources */, - 04540D6127DD08C300E91B77 /* CodeFile.swift in Sources */, 04660F6627E3ACEF00477777 /* ReopenBehavior.swift in Sources */, 043C321627E3201F006AE443 /* WorkspaceDocument.swift in Sources */, - 28B0A19827E385C300B73177 /* SideBarToolbarTop.swift in Sources */, + 28B0A19827E385C300B73177 /* SideBarToolbarTop.swift in Sources */, 345F667527DF6C180069BD69 /* FileTabRow.swift in Sources */, - 043C321827E32246006AE443 /* CodeFileEditor.swift in Sources */, 04540D6327DD08C300E91B77 /* AppDelegate.swift in Sources */, - 0439FEF527DD104500528317 /* WorkspaceEditorView.swift in Sources */, 28FFE1BF27E3A441001939DB /* SideBarToolbarBottom.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -778,6 +757,10 @@ isa = XCSwiftPackageProductDependency; productName = WorkspaceClient; }; + 5CF38A5D27E48E6C0096A0F7 /* CodeFile */ = { + isa = XCSwiftPackageProductDependency; + productName = CodeFile; + }; /* End XCSwiftPackageProductDependency section */ }; rootObject = B658FB2427DA9E0F00EA4DBD /* Project object */; diff --git a/CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved new file mode 100644 index 0000000000..8450bfb4c9 --- /dev/null +++ b/CodeEdit.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -0,0 +1,34 @@ +{ + "object": { + "pins": [ + { + "package": "CodeEditor", + "repositoryURL": "https://github.com/ZeeZide/CodeEditor.git", + "state": { + "branch": null, + "revision": "5856fac22b0a2174dbdea212784567c8c9cd1129", + "version": "1.2.0" + } + }, + { + "package": "Highlightr", + "repositoryURL": "https://github.com/raspu/Highlightr", + "state": { + "branch": null, + "revision": "93199b9e434f04bda956a613af8f571933f9f037", + "version": "2.1.2" + } + }, + { + "package": "SnapshotTesting", + "repositoryURL": "https://github.com/pointfreeco/swift-snapshot-testing.git", + "state": { + "branch": null, + "revision": "f8a9c997c3c1dab4e216a8ec9014e23144cbab37", + "version": "1.9.0" + } + } + ] + }, + "version": 1 +} diff --git a/CodeEdit/ContentView.swift b/CodeEdit/ContentView.swift index c8a92524e8..78822650a1 100644 --- a/CodeEdit/ContentView.swift +++ b/CodeEdit/ContentView.swift @@ -7,6 +7,7 @@ import SwiftUI import WorkspaceClient +import CodeEditorView struct WorkspaceView: View { @State private var directoryURL: URL? @@ -57,7 +58,8 @@ struct WorkspaceView: View { ZStack { if let selectedId = selectedId { if let selectedItem = try? workspaceClient.getFileItem(selectedId) { - WorkspaceEditorView(item: selectedItem) + CodeEditorView(fileURL: selectedItem.url) + .padding(.top, 31.0) } } diff --git a/CodeEdit/Documents/CodeEditDocumentController.swift b/CodeEdit/Documents/CodeEditDocumentController.swift index 7f19cf3d8e..5fc822f5ff 100644 --- a/CodeEdit/Documents/CodeEditDocumentController.swift +++ b/CodeEdit/Documents/CodeEditDocumentController.swift @@ -8,9 +8,7 @@ import Cocoa class CodeEditDocumentController: NSDocumentController { - override func openDocument(_ sender: Any?) { - print("Opening") - + override func openDocument(_ sender: Any?) { let dialog = NSOpenPanel() dialog.title = "Open Workspace or File" diff --git a/CodeEdit/Documents/CodeFile.swift b/CodeEdit/Documents/CodeFile.swift deleted file mode 100644 index c5b90a6a2d..0000000000 --- a/CodeEdit/Documents/CodeFile.swift +++ /dev/null @@ -1,50 +0,0 @@ -// -// CodeFile.swift -// CodeEdit -// -// Created by Rehatbir Singh on 12/03/2022. -// - -import Foundation -import AppKit -import SwiftUI - -enum CodeFileError: Error { - case failedToDecode - case failedToEncode -} - -@objc(CodeFile) -class CodeFile: NSDocument, ObservableObject { - - @Published var text = "" - - // MARK: - NSDocument - - override class var autosavesInPlace: Bool { - return true - } - - override func makeWindowControllers() { - // Returns the Storyboard that contains your Document window. - let contentView = CodeFileEditor(file: self) - let window = NSWindow( - contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), - styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], - backing: .buffered, defer: false) - window.center() - window.contentView = NSHostingView(rootView: contentView) - let windowController = NSWindowController(window: window) - self.addWindowController(windowController) - } - - override func data(ofType typeName: String) throws -> Data { - guard let data = self.text.data(using: .utf8) else { throw CodeFileError.failedToEncode } - return data - } - - override func read(from data: Data, ofType typeName: String) throws { - guard let text = String(data: data, encoding: .utf8) else { throw CodeFileError.failedToDecode } - self.text = text - } -} diff --git a/CodeEdit/Documents/WorkspaceDocument.swift b/CodeEdit/Documents/WorkspaceDocument.swift index ad1f7d86f7..91321136d6 100644 --- a/CodeEdit/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Documents/WorkspaceDocument.swift @@ -10,6 +10,7 @@ import AppKit import SwiftUI import WorkspaceClient import Combine +import CodeFile @objc(WorkspaceDocument) class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { @@ -19,12 +20,23 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { @Published var openFileItems: [WorkspaceClient.FileItem] = [] @Published var sortFoldersOnTop: Bool = true @Published var fileItems: [WorkspaceClient.FileItem] = [] - var openedCodeFiles: [WorkspaceClient.FileItem : CodeFile] = [:] - private var cancellable: AnyCancellable? + var openedCodeFiles: [WorkspaceClient.FileItem : CodeFileDocument] = [:] + private var cancellables = Set() deinit { - cancellable?.cancel() - cancellable = nil + cancellables.forEach { $0.cancel() } + } + + override init() { + super.init() +// $selectedId +// .sink { [weak self] id in +// guard let self = self else { return } +// if let item = self.fileItems.filter { $0.id == id }.first { +// self.openFile(item: item) +// } +// } +// .store(in: &cancellables) } func closeFileTab(item: WorkspaceClient.FileItem) { @@ -47,7 +59,7 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { func openFile(item: WorkspaceClient.FileItem) { do { - let codeFile = try CodeFile(for: item.url, withContentsOf: item.url, ofType: "public.source-code") + let codeFile = try CodeFileDocument(for: item.url, withContentsOf: item.url, ofType: "public.source-code") if !openFileItems.contains(item) { openFileItems.append(item) @@ -95,15 +107,10 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { folderURL: url, ignoredFilesAndFolders: ignoredFilesAndDirectory ) - cancellable = workspaceClient? + workspaceClient? .getFiles .sink { [weak self] files in guard let self = self else { return } - -// defer { -// // this sorts the array alphabetically -// self.fileItems = self.fileItems.sorted() -// } guard !self.fileItems.isEmpty else { self.fileItems = files @@ -124,6 +131,7 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { } } } + .store(in: &cancellables) } override func write(to url: URL, ofType typeName: String) throws {} diff --git a/CodeEdit/Editor/CodeFileEditor.swift b/CodeEdit/Editor/CodeFileEditor.swift deleted file mode 100644 index 6def3894b9..0000000000 --- a/CodeEdit/Editor/CodeFileEditor.swift +++ /dev/null @@ -1,22 +0,0 @@ -// -// CodeFileEditor.swift -// CodeEdit -// -// Created by Pavel Kasila on 17.03.22. -// - -import SwiftUI - -struct CodeFileEditor: View { - @ObservedObject var file: CodeFile - - var body: some View { - EditorView(text: $file.text) - } -} - -struct CodeFileEditor_Previews: PreviewProvider { - static var previews: some View { - CodeFileEditor(file: .init()) - } -} diff --git a/CodeEdit/Editor/EditorView.swift b/CodeEdit/Editor/EditorView.swift deleted file mode 100644 index 77f47be3e3..0000000000 --- a/CodeEdit/Editor/EditorView.swift +++ /dev/null @@ -1,24 +0,0 @@ -// -// EditorView.swift -// CodeEdit -// -// Created by Pavel Kasila on 12.03.22. -// - -import SwiftUI - -struct EditorView: View { - @Binding var text: String - - var body: some View { - TextEditor(text: $text) - .disableAutocorrection(true) - .font(.callout.monospaced()) - } -} - -struct EditorView_Previews: PreviewProvider { - static var previews: some View { - EditorView(text: .constant("Hello, world!")) - } -} diff --git a/CodeEdit/Editor/WorkspaceEditorView.swift b/CodeEdit/Editor/WorkspaceEditorView.swift deleted file mode 100644 index 9a179bbf41..0000000000 --- a/CodeEdit/Editor/WorkspaceEditorView.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// WorkspaceEditorView.swift -// CodeEdit -// -// Created by Pavel Kasila on 12.03.22. -// - -import SwiftUI -import WorkspaceClient - -struct WorkspaceEditorView: View { - @ObservedObject var workspace: WorkspaceDocument - var item: WorkspaceClient.FileItem - var windowController: NSWindowController - - var body: some View { - ScrollView(.vertical, showsIndicators: true) { - if let file = workspace.openedCodeFiles[item] { - CodeFileEditor(file: file) - } else { - Text("File cannot be opened") - } - } - .background(Color(nsColor: NSColor.textBackgroundColor)) - .onAppear { workspace.openFile(item: item) } - } -} diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 9b44fcf5b3..8bd6939483 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -30,7 +30,7 @@ public.source-code NSDocumentClass - CodeFile + CodeFileDocument CFBundleURLTypes diff --git a/CodeEdit/SideBar/SideBar.swift b/CodeEdit/SideBar/SideBar.swift index ae4bad12ff..5ab819811f 100644 --- a/CodeEdit/SideBar/SideBar.swift +++ b/CodeEdit/SideBar/SideBar.swift @@ -13,8 +13,8 @@ struct SideBar: View { var windowController: NSWindowController @State private var selection: Int = 0 - var body: some View { - List { + var body: some View { + List { switch selection { case 0: Section(header: Text(workspace.fileURL?.lastPathComponent ?? "Unknown")) { @@ -28,13 +28,13 @@ struct SideBar: View { } default: EmptyView() } - } - .safeAreaInset(edge: .top) { - SideBarToolbarTop(selection: $selection) - .padding(.bottom, -8) - } - .safeAreaInset(edge: .bottom) { - SideBarToolbarBottom(workspace: workspace) - } - } + } + .safeAreaInset(edge: .top) { + SideBarToolbarTop(selection: $selection) + .padding(.bottom, -8) + } + .safeAreaInset(edge: .bottom) { + SideBarToolbarBottom(workspace: workspace) + } + } } diff --git a/CodeEdit/SideBar/SideBarItem.swift b/CodeEdit/SideBar/SideBarItem.swift index e552268d96..26eb5177a1 100644 --- a/CodeEdit/SideBar/SideBarItem.swift +++ b/CodeEdit/SideBar/SideBarItem.swift @@ -7,6 +7,7 @@ import SwiftUI import WorkspaceClient +import CodeFile struct SideBarItem: View { @@ -25,19 +26,26 @@ struct SideBarItem: View { } } - func sidebarFileItem(_ item: WorkspaceClient.FileItem) -> some View { + func sidebarFileItem(_ item: WorkspaceClient.FileItem) -> some View { NavigationLink(tag: item.id, selection: $workspace.selectedId) { - WorkspaceEditorView(workspace: workspace, item: item, windowController: windowController) - .safeAreaInset(edge: .top) { - TabBar(windowController: windowController, workspace: workspace) - } - } label: { + ZStack { + if let codeFile = workspace.openedCodeFiles[item] { + CodeFileView(codeFile: codeFile) + .safeAreaInset(edge: .top) { + TabBar(windowController: windowController, workspace: workspace) + } + } else { + Text("File cannot be opened") + } + } + .onAppear { workspace.openFile(item: item) } + } label: { Label(item.url.lastPathComponent, systemImage: item.systemImage) .accentColor(.secondary) .font(.callout) } .buttonStyle(.plain) - } + } func sidebarFolderItem(_ item: WorkspaceClient.FileItem) -> some View { DisclosureGroup(isExpanded: $isExpanded) { diff --git a/CodeEditModules/Modules/CodeFile/Tests/UnitTests.swift b/CodeEditModules/Modules/CodeFile/Tests/UnitTests.swift new file mode 100644 index 0000000000..6d7cb94c4f --- /dev/null +++ b/CodeEditModules/Modules/CodeFile/Tests/UnitTests.swift @@ -0,0 +1,33 @@ +// +// UnitTests.swift +// CodeEdit +// +// Created by Marco Carnevali on 18/03/22. +// +@testable import CodeFile +import Foundation +import SnapshotTesting +import SwiftUI +import XCTest + +final class CodeFileUnitTests: XCTestCase { + func testViewSnapshot() throws { + let directory = try FileManager.default.url(for: .developerApplicationDirectory, in: .userDomainMask, appropriateFor: nil, create: true) + .appendingPathComponent("CodeEdit", isDirectory: true) + .appendingPathComponent("WorkspaceClientTests", isDirectory: true) + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + + let fileURL = directory.appendingPathComponent("fakeFile.md") + + try "Fake String".data(using: .utf8)?.write(to: fileURL) + let codeFile = try CodeFileDocument( + for: fileURL, + withContentsOf: fileURL, + ofType: "public.source-code" + ) + let view = CodeFileView(codeFile: codeFile) + let hosting = NSHostingView(rootView: view) + hosting.frame = CGRect(x: 0, y: 0, width: 100, height: 100) + assertSnapshot(matching: hosting, as: .image) + } +} diff --git a/CodeEditModules/Modules/CodeFile/Tests/__Snapshots__/UnitTests/testViewSnapshot.1.png b/CodeEditModules/Modules/CodeFile/Tests/__Snapshots__/UnitTests/testViewSnapshot.1.png new file mode 100644 index 0000000000000000000000000000000000000000..5ff74e142cccc6fa77388b983bcd120ee93173c2 GIT binary patch literal 6125 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 CodeEditor.Language { + if let fileURL = fileURL { + return .init(url: fileURL) + } else { + return .markdown + } + } + + override public func makeWindowControllers() { + // Returns the Storyboard that contains your Document window. + let contentView = CodeFileView(codeFile: self) + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 800, height: 600), + styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView], + backing: .buffered, defer: false + ) + window.center() + window.contentView = NSHostingView(rootView: contentView) + let windowController = NSWindowController(window: window) + addWindowController(windowController) + } + + override public func data(ofType _: String) throws -> Data { + guard let data = content.data(using: .utf8) else { throw CodeFileError.failedToEncode } + return data + } + + override public func read(from data: Data, ofType _: String) throws { + guard let content = String(data: data, encoding: .utf8) else { throw CodeFileError.failedToDecode } + self.content = content + } +} + +private extension CodeEditor.Language { + init(url: URL) { + var value = url.pathExtension + switch value { + case "js": value = "javascript" + case "sh": value = "shell" + default: break + } + self.init(rawValue: value) + } +} diff --git a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem+Array.swift b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem+Array.swift index 8ac175eb9a..10e2e0be6c 100644 --- a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem+Array.swift +++ b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem+Array.swift @@ -1,25 +1,25 @@ // // File.swift -// +// // // Created by Lukas Pistrol on 17.03.22. // import Foundation -extension Array where Element == WorkspaceClient.FileItem { - public func sortItems(foldersOnTop: Bool) -> Self { - var alphabetically = self.sorted { $0.fileName < $1.fileName } +public extension Array where Element == WorkspaceClient.FileItem { + func sortItems(foldersOnTop: Bool) -> Self { + var alphabetically = sorted { $0.fileName < $1.fileName } - if foldersOnTop { - var foldersOnTop = alphabetically.filter { $0.children != nil } - alphabetically.removeAll { $0.children != nil } + if foldersOnTop { + var foldersOnTop = alphabetically.filter { $0.children != nil } + alphabetically.removeAll { $0.children != nil } - foldersOnTop.append(contentsOf: alphabetically) + foldersOnTop.append(contentsOf: alphabetically) - return foldersOnTop - } else { - return alphabetically - } - } + return foldersOnTop + } else { + return alphabetically + } + } } diff --git a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift index 3c63ca6ebf..0f34b96c07 100644 --- a/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift +++ b/CodeEditModules/Modules/WorkspaceClient/src/Model/FileItem.swift @@ -21,10 +21,11 @@ public extension WorkspaceClient { return children.isEmpty ? "folder" : "folder.fill" } } - public var fileName: String { - url.lastPathComponent - } - + + public var fileName: String { + url.lastPathComponent + } + public var fileIcon: String { switch fileType { case "json", "js": @@ -68,13 +69,14 @@ public extension WorkspaceClient { ) { self.url = url self.children = children - self.id = url.relativePath + id = url.relativePath } - - public static func ==(lhs: FileItem, rhs: FileItem) -> Bool { + + public static func == (lhs: FileItem, rhs: FileItem) -> Bool { return lhs.id == rhs.id } - public static func <(lhs: FileItem, rhs: FileItem) -> Bool { + + public static func < (lhs: FileItem, rhs: FileItem) -> Bool { return lhs.url.lastPathComponent < rhs.url.lastPathComponent } } diff --git a/CodeEditModules/Package.swift b/CodeEditModules/Package.swift index 8ae0a07f3a..1d03d550e1 100644 --- a/CodeEditModules/Package.swift +++ b/CodeEditModules/Package.swift @@ -6,15 +6,30 @@ let package = Package( name: "CodeEditModules", defaultLocalization: "en", platforms: [ - .macOS(.v10_15), + .macOS(.v11), ], products: [ .library( name: "WorkspaceClient", targets: ["WorkspaceClient"] ), + .library( + name: "CodeFile", + targets: ["CodeFile"] + ), + ], + dependencies: [ + .package( + name: "CodeEditor", + url: "https://github.com/ZeeZide/CodeEditor.git", + from: "1.2.0" + ), + .package( + name: "SnapshotTesting", + url: "https://github.com/pointfreeco/swift-snapshot-testing.git", + from: "1.9.0" + ), ], - dependencies: [], targets: [ .target( name: "WorkspaceClient", @@ -27,5 +42,20 @@ let package = Package( ], path: "Modules/WorkspaceClient/Tests" ), + .target( + name: "CodeFile", + dependencies: [ + "CodeEditor", + ], + path: "Modules/CodeFile/src" + ), + .testTarget( + name: "CodeFileTests", + dependencies: [ + "CodeFile", + "SnapshotTesting", + ], + path: "Modules/CodeFile/Tests" + ), ] )