diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 379f795b7..48f4613e2 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -16,6 +16,11 @@ 04660F6427E3ACAF00477777 /* Appearances.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04660F6327E3ACAF00477777 /* Appearances.swift */; }; 04660F6627E3ACEF00477777 /* ReopenBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04660F6527E3ACEF00477777 /* ReopenBehavior.swift */; }; 04660F6A27E51E5C00477777 /* CodeEditWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */; }; + 0485EB1927E70F4900138301 /* QuickOpenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB1827E70F4900138301 /* QuickOpenView.swift */; }; + 0485EB1D27E7338100138301 /* QuickOpenItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB1C27E7338100138301 /* QuickOpenItem.swift */; }; + 0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */; }; + 0485EB2327E7791400138301 /* QuickOpenPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0485EB2227E7791400138301 /* QuickOpenPreviewView.swift */; }; + 0485EB2527E7B9C800138301 /* Overlays in Frameworks */ = {isa = PBXBuildFile; productRef = 0485EB2427E7B9C800138301 /* Overlays */; }; 286620A527E4AB6900E18C2B /* BreadcrumbsComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 286620A427E4AB6900E18C2B /* BreadcrumbsComponent.swift */; }; 2875A46D27E3BE5B007805F8 /* BreadcrumbsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2875A46C27E3BE5B007805F8 /* BreadcrumbsView.swift */; }; 287776E727E3413200D46668 /* SideBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 287776E627E3413200D46668 /* SideBar.swift */; }; @@ -68,6 +73,10 @@ 04660F6527E3ACEF00477777 /* ReopenBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReopenBehavior.swift; sourceTree = ""; }; 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditWindowController.swift; sourceTree = ""; }; 0468438427DC76E200F8E88E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 0485EB1827E70F4900138301 /* QuickOpenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickOpenView.swift; sourceTree = ""; }; + 0485EB1C27E7338100138301 /* QuickOpenItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickOpenItem.swift; sourceTree = ""; }; + 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkspaceCodeFileView.swift; sourceTree = ""; }; + 0485EB2227E7791400138301 /* QuickOpenPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickOpenPreviewView.swift; sourceTree = ""; }; 04ADA0C827E5D29000BF00B2 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/Localizable.strings; sourceTree = ""; }; 04ADA0CA27E5D41F00BF00B2 /* be */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = be; path = be.lproj/Localizable.strings; sourceTree = ""; }; 04F2BF0E27DBB28E0024EAB1 /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; @@ -106,6 +115,7 @@ buildActionMask = 2147483647; files = ( 5C403B8F27E20F8000788241 /* WorkspaceClient in Frameworks */, + 0485EB2527E7B9C800138301 /* Overlays in Frameworks */, B65E614627E6765D00255275 /* Introspect in Frameworks */, D70F5E2C27E4E8CF004EE4B9 /* WelcomeModule in Frameworks */, 28CE5EA027E6493D0065D29C /* StatusBar in Frameworks */, @@ -136,6 +146,7 @@ 043C321327E31FF6006AE443 /* CodeEditDocumentController.swift */, 043C321527E3201F006AE443 /* WorkspaceDocument.swift */, 04660F6927E51E5C00477777 /* CodeEditWindowController.swift */, + 0485EB1E27E7458B00138301 /* WorkspaceCodeFileView.swift */, ); path = Documents; sourceTree = ""; @@ -150,6 +161,16 @@ path = Models; sourceTree = ""; }; + 0485EB1727E7016400138301 /* Quick Open */ = { + isa = PBXGroup; + children = ( + 0485EB1827E70F4900138301 /* QuickOpenView.swift */, + 0485EB1C27E7338100138301 /* QuickOpenItem.swift */, + 0485EB2227E7791400138301 /* QuickOpenPreviewView.swift */, + ); + path = "Quick Open"; + sourceTree = ""; + }; 04F2BF1027DBB3AF0024EAB1 /* Settings */ = { isa = PBXGroup; children = ( @@ -234,6 +255,7 @@ B658FB2E27DA9E0F00EA4DBD /* CodeEdit */ = { isa = PBXGroup; children = ( + 0485EB1727E7016400138301 /* Quick Open */, 04660F6027E3A68A00477777 /* Info.plist */, D72E1A8127E3B0A300EB11B9 /* Welcome */, 043C321227E31FE8006AE443 /* Documents */, @@ -305,6 +327,7 @@ D70F5E2B27E4E8CF004EE4B9 /* WelcomeModule */, B65E614527E6765D00255275 /* Introspect */, 28CE5E9F27E6493D0065D29C /* StatusBar */, + 0485EB2427E7B9C800138301 /* Overlays */, ); productName = CodeEdit; productReference = B658FB2C27DA9E0F00EA4DBD /* CodeEdit.app */; @@ -471,6 +494,7 @@ files = ( 2B7A583527E4BA0100D25D4E /* AppDelegate.swift in Sources */, 2875A46D27E3BE5B007805F8 /* BreadcrumbsView.swift in Sources */, + 0485EB1D27E7338100138301 /* QuickOpenItem.swift in Sources */, 04540D5B27DD08C300E91B77 /* SettingsView.swift in Sources */, D72E1A8927E44D7C00EB11B9 /* WelcomeWindowView.swift in Sources */, 04540D5C27DD08C300E91B77 /* GeneralSettingsView.swift in Sources */, @@ -480,6 +504,8 @@ 34EE19BE27E0469C00F152CE /* BlurView.swift in Sources */, D7211D4327E066CE008F2ED7 /* Localized+Ex.swift in Sources */, 287776E927E34BC700D46668 /* TabBar.swift in Sources */, + 0485EB1F27E7458B00138301 /* WorkspaceCodeFileView.swift in Sources */, + 0485EB1927E70F4900138301 /* QuickOpenView.swift in Sources */, 286620A527E4AB6900E18C2B /* BreadcrumbsComponent.swift in Sources */, 287776EF27E3515300D46668 /* TabBarItem.swift in Sources */, D72E1A8727E4242900EB11B9 /* RecentProjectsView.swift in Sources */, @@ -493,6 +519,7 @@ 28B0A19827E385C300B73177 /* SideBarToolbarTop.swift in Sources */, 345F667527DF6C180069BD69 /* FileTabRow.swift in Sources */, 28FFE1BF27E3A441001939DB /* SideBarToolbarBottom.swift in Sources */, + 0485EB2327E7791400138301 /* QuickOpenPreviewView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -870,6 +897,10 @@ /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ + 0485EB2427E7B9C800138301 /* Overlays */ = { + isa = XCSwiftPackageProductDependency; + productName = Overlays; + }; 28CE5E9F27E6493D0065D29C /* StatusBar */ = { isa = XCSwiftPackageProductDependency; productName = StatusBar; diff --git a/CodeEdit/AppDelegate.swift b/CodeEdit/AppDelegate.swift index 2521b4b00..f68421ace 100644 --- a/CodeEdit/AppDelegate.swift +++ b/CodeEdit/AppDelegate.swift @@ -46,6 +46,47 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { return true } + func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { + if flag { + return false + } + + handleOpen() + + return false + } + + func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool { + return false + } + + func handleOpen() { + let behavior = ReopenBehavior(rawValue: UserDefaults.standard.string(forKey: ReopenBehavior.storageKey) + ?? ReopenBehavior.default.rawValue) ?? ReopenBehavior.default + + switch behavior { + case .welcome: + openWelcome(self) + case .openPanel: + CodeEditDocumentController.shared.openDocument(self) + case .newDocument: + CodeEditDocumentController.shared.newDocument(self) + } + } + + func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { + CodeEditDocumentController.shared.documents.flatMap { doc in + return doc.windowControllers + }.forEach { windowContoller in + if let windowContoller = windowContoller as? CodeEditWindowController { + windowContoller.workspace?.close() + } + } + return .terminateNow + } + + // MARK: - Open windows + @IBAction func openPreferences(_ sender: Any) { if let window = NSApp.windows.filter({ window in return (window.contentView as? NSHostingView) != nil @@ -86,43 +127,4 @@ class AppDelegate: NSObject, NSApplicationDelegate, ObservableObject { window.contentView = NSHostingView(rootView: contentView) window.makeKeyAndOrderFront(self) } - - func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool { - if flag { - return false - } - - handleOpen() - - return false - } - - func applicationShouldOpenUntitledFile(_ sender: NSApplication) -> Bool { - return false - } - - func handleOpen() { - let behavior = ReopenBehavior(rawValue: UserDefaults.standard.string(forKey: ReopenBehavior.storageKey) - ?? ReopenBehavior.default.rawValue) ?? ReopenBehavior.default - - switch behavior { - case .welcome: - openWelcome(self) - case .openPanel: - CodeEditDocumentController.shared.openDocument(self) - case .newDocument: - CodeEditDocumentController.shared.newDocument(self) - } - } - - func applicationShouldTerminate(_ sender: NSApplication) -> NSApplication.TerminateReply { - CodeEditDocumentController.shared.documents.flatMap { doc in - return doc.windowControllers - }.forEach { windowContoller in - if let windowContoller = windowContoller as? CodeEditWindowController { - windowContoller.workspace?.close() - } - } - return .terminateNow - } } diff --git a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift index 10fb5ec07..833818d05 100644 --- a/CodeEdit/Breadcrumbs/BreadcrumbsView.swift +++ b/CodeEdit/Breadcrumbs/BreadcrumbsView.swift @@ -60,7 +60,7 @@ struct BreadcrumbsView: View { } private func fileInfo() { - guard let projName = workspace.folderURL?.lastPathComponent, + guard let projName = workspace.fileURL?.lastPathComponent, var components = file.url.pathComponents.split(separator: projName).last else { return } components.removeLast() diff --git a/CodeEdit/Documents/CodeEditWindowController.swift b/CodeEdit/Documents/CodeEditWindowController.swift index 03658e539..8f1649baf 100644 --- a/CodeEdit/Documents/CodeEditWindowController.swift +++ b/CodeEdit/Documents/CodeEditWindowController.swift @@ -6,7 +6,9 @@ // import Cocoa +import SwiftUI import CodeFile +import Overlays class CodeEditWindowController: NSWindowController { @@ -31,4 +33,23 @@ class CodeEditWindowController: NSWindowController { @IBAction func saveDocument(_ sender: Any) { getSelectedCodeFile()?.save(sender) } + + @IBAction func openQuickly(_ sender: Any) { + if let workspace = workspace, let state = workspace.quickOpenState { + if let window = window?.childWindows?.filter({ window in + return (window.contentView as? NSHostingView) != nil + }).first { + window.close() + return + } + + let panel = OverlayPanel() + let contentView = QuickOpenView(state: state) { + panel.close() + } + panel.contentView = NSHostingView(rootView: contentView) + window?.addChildWindow(panel, ordered: .above) + panel.makeKeyAndOrderFront(self) + } + } } diff --git a/CodeEdit/Documents/WorkspaceCodeFileView.swift b/CodeEdit/Documents/WorkspaceCodeFileView.swift new file mode 100644 index 000000000..2a6db0fdc --- /dev/null +++ b/CodeEdit/Documents/WorkspaceCodeFileView.swift @@ -0,0 +1,40 @@ +// +// WorkspaceCodeFileEditor.swift +// CodeEdit +// +// Created by Pavel Kasila on 20.03.22. +// + +import SwiftUI +import CodeFile +import WorkspaceClient +import StatusBar + +struct WorkspaceCodeFileView: View { + var windowController: NSWindowController + @ObservedObject var workspace: WorkspaceDocument + + @ViewBuilder var body: some View { + if let item = workspace.openFileItems.first(where: { file in + return file.id == workspace.selectedId + }) { + if let codeFile = workspace.openedCodeFiles[item] { + CodeFileView(codeFile: codeFile) + .safeAreaInset(edge: .top, spacing: 0) { + VStack(spacing: 0) { + TabBar(windowController: windowController, workspace: workspace) + CustomDivider() + BreadcrumbsView(item, workspace: workspace) + } + } + .safeAreaInset(edge: .bottom) { + StatusBarView() + } + } else { + Text("CodeEdit cannot open this file because its file type is not supported.") + } + } else { + Text("Open file from sidebar") + } + } +} diff --git a/CodeEdit/Documents/WorkspaceDocument.swift b/CodeEdit/Documents/WorkspaceDocument.swift index 755973fc1..ed7418b19 100644 --- a/CodeEdit/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Documents/WorkspaceDocument.swift @@ -14,14 +14,16 @@ import CodeFile @objc(WorkspaceDocument) class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { - @Published var workspaceClient: WorkspaceClient? + var workspaceClient: WorkspaceClient? + @Published var selectedId: String? @Published var openFileItems: [WorkspaceClient.FileItem] = [] @Published var sortFoldersOnTop: Bool = true @Published var fileItems: [WorkspaceClient.FileItem] = [] + var quickOpenState: QuickOpenState? + var openedCodeFiles: [WorkspaceClient.FileItem: CodeFileDocument] = [:] - var folderURL: URL? private var cancellables = Set() deinit { @@ -36,11 +38,10 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { guard let idx = openFileItems.firstIndex(of: item) else { return } let closedFileItem = openFileItems.remove(at: idx) - guard closedFileItem.id == selectedId else { return } + guard closedFileItem.id == item.id else { return } if openFileItems.isEmpty { selectedId = nil - self.windowControllers.first?.document = self } else if idx == 0 { selectedId = openFileItems.first?.id } else { @@ -101,12 +102,12 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { } override func read(from url: URL, ofType typeName: String) throws { - self.folderURL = url self.workspaceClient = try .default( fileManager: .default, folderURL: url, ignoredFilesAndFolders: ignoredFilesAndDirectory ) + self.quickOpenState = .init(self) workspaceClient? .getFiles .sink { [weak self] files in @@ -146,3 +147,58 @@ class WorkspaceDocument: NSDocument, ObservableObject, NSToolbarDelegate { super.close() } } + +// MARK: - Quick Open + +extension WorkspaceDocument { + + class QuickOpenState: ObservableObject { + init(_ workspace: WorkspaceDocument) { + self.workspace = workspace + } + + var workspace: WorkspaceDocument + + @Published var openQuicklyQuery: String = "" + @Published var openQuicklyFiles: [WorkspaceClient.FileItem] = [] + @Published var isShowingOpenQuicklyFiles: Bool = false + + func fetchOpenQuickly() { + if openQuicklyQuery == "" { + openQuicklyFiles = [] + self.isShowingOpenQuicklyFiles = !openQuicklyFiles.isEmpty + return + } + + DispatchQueue(label: "austincondiff.CodeEdit.quickOpen.searchFiles").async { + if let url = self.workspace.fileURL { + let enumerator = FileManager.default.enumerator(at: url, + includingPropertiesForKeys: [ + .isRegularFileKey + ], + options: [ + .skipsHiddenFiles, + .skipsPackageDescendants + ]) + if let filePaths = enumerator?.allObjects as? [URL] { + let files = filePaths.filter { url in + let state1 = url.lastPathComponent.lowercased().contains(self.openQuicklyQuery.lowercased()) + do { + let values = try url.resourceValues(forKeys: [.isRegularFileKey]) + return state1 && (values.isRegularFile ?? false) + } catch { + return false + } + }.map { url in + WorkspaceClient.FileItem(url: url, children: nil) + } + DispatchQueue.main.async { + self.openQuicklyFiles = files + self.isShowingOpenQuicklyFiles = !self.openQuicklyFiles.isEmpty + } + } + } + } + } + } +} diff --git a/CodeEdit/MainMenu.xib b/CodeEdit/MainMenu.xib index 7460f6d53..605193b44 100644 --- a/CodeEdit/MainMenu.xib +++ b/CodeEdit/MainMenu.xib @@ -85,6 +85,11 @@ + + + + + diff --git a/CodeEdit/Quick Open/QuickOpenItem.swift b/CodeEdit/Quick Open/QuickOpenItem.swift new file mode 100644 index 000000000..f802afa8e --- /dev/null +++ b/CodeEdit/Quick Open/QuickOpenItem.swift @@ -0,0 +1,33 @@ +// +// QuickOpenItem.swift +// CodeEdit +// +// Created by Pavel Kasila on 20.03.22. +// + +import SwiftUI +import WorkspaceClient + +public struct QuickOpenItem: View { + let baseDirectory: URL + let fileItem: WorkspaceClient.FileItem + + public var body: some View { + HStack(spacing: 8) { + Image(nsImage: NSWorkspace.shared.icon(forFile: fileItem.url.path)) + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 32, height: 32) + VStack(alignment: .leading) { + Text(fileItem.url.lastPathComponent).font(.system(size: 13)) + .lineLimit(1) + Text(fileItem.url.path.replacingOccurrences(of: baseDirectory.path, with: "")) + .font(.system(size: 11)) + .lineLimit(1) + .truncationMode(.tail) + }.padding(.trailing, 15) + Spacer() + } + .contentShape(Rectangle()) + } +} diff --git a/CodeEdit/Quick Open/QuickOpenPreviewView.swift b/CodeEdit/Quick Open/QuickOpenPreviewView.swift new file mode 100644 index 000000000..79cba2f5a --- /dev/null +++ b/CodeEdit/Quick Open/QuickOpenPreviewView.swift @@ -0,0 +1,45 @@ +// +// QuickOpenPreviewView.swift +// CodeEdit +// +// Created by Pavel Kasila on 20.03.22. +// + +import SwiftUI +import WorkspaceClient +import CodeFile +import CodeEditor + +struct QuickOpenPreviewView: View { + var item: WorkspaceClient.FileItem + @State var content: String = "" + @State var loaded = false + @State var error: String? + + var body: some View { + VStack { + if loaded { + ThemedCodeView($content, language: .init(url: item.url), editable: false) + } else if let error = error { + Text(error) + } else { + ProgressView() + } + } + .onAppear { + loaded = false + error = nil + DispatchQueue(label: "austincondiff.CodeEdit.quickOpen.preview").async { + do { + let data = try String(contentsOf: item.url) + DispatchQueue.main.async { + self.content = data + self.loaded = true + } + } catch let error { + self.error = error.localizedDescription + } + } + } + } +} diff --git a/CodeEdit/Quick Open/QuickOpenView.swift b/CodeEdit/Quick Open/QuickOpenView.swift new file mode 100644 index 000000000..af1008dc1 --- /dev/null +++ b/CodeEdit/Quick Open/QuickOpenView.swift @@ -0,0 +1,76 @@ +// +// QuickOpenView.swift +// CodeEdit +// +// Created by Pavel Kasila on 20.03.22. +// + +import SwiftUI +import WorkspaceClient + +struct QuickOpenView: View { + @ObservedObject var state: WorkspaceDocument.QuickOpenState + @State var selectedItem: WorkspaceClient.FileItem? + var onClose: () -> Void + + var body: some View { + VStack(spacing: 0.0) { + VStack { + HStack(alignment: .center, spacing: 0) { + Image(systemName: "doc.text.magnifyingglass") + .imageScale(.large) + .padding(.horizontal, 20) + .offset(x: 2, y: 0) + TextField("Open Quickly", text: $state.openQuicklyQuery) + .font(.system(size: 22, weight: .light, design: .default)) + .textFieldStyle(.plain) + .onReceive( + state.$openQuicklyQuery + .debounce(for: .seconds(0.4), scheduler: DispatchQueue.main) + ) { _ in + state.fetchOpenQuickly() + } + } + .frame(height: 28) + .padding(.vertical) + .foregroundColor(.primary.opacity(0.85)) + .background(BlurView(material: .sidebar, blendingMode: .behindWindow)) + } + Divider() + NavigationView { + List(state.openQuicklyFiles, id: \.id) { file in + NavigationLink(tag: file, selection: $selectedItem) { + QuickOpenPreviewView(item: file) + } label: { + QuickOpenItem(baseDirectory: state.workspace.fileURL!, fileItem: file) + } + .onTapGesture(count: 2) { + state.workspace.openFile(item: file) + self.onClose() + } + .onTapGesture(count: 1) { + self.selectedItem = file + } + } + .removeBackground() + .frame(minWidth: 250, maxWidth: 250) + if state.openQuicklyFiles.isEmpty { + EmptyView() + } else { + Text("Select a file to preview") + } + } + } + .background(BlurView(material: .sidebar, blendingMode: .behindWindow)) + .edgesIgnoringSafeArea(.vertical) + .frame(minWidth: 600, + minHeight: 400, + maxHeight: .infinity) + } +} + +struct QuickOpenView_Previews: PreviewProvider { + static var previews: some View { + QuickOpenView(state: .init(.init()), onClose: {}) + } +} diff --git a/CodeEdit/SideBar/SideBarItem.swift b/CodeEdit/SideBar/SideBarItem.swift index 53accca17..f6efdd040 100644 --- a/CodeEdit/SideBar/SideBarItem.swift +++ b/CodeEdit/SideBar/SideBarItem.swift @@ -8,7 +8,6 @@ import SwiftUI import WorkspaceClient import CodeFile -import StatusBar struct SideBarItem: View { @@ -30,25 +29,10 @@ struct SideBarItem: View { } func sidebarFileItem(_ item: WorkspaceClient.FileItem) -> some View { - NavigationLink(tag: item.id, selection: $workspace.selectedId) { - ZStack { - if let codeFile = workspace.openedCodeFiles[item] { - CodeFileView(codeFile: codeFile) - .safeAreaInset(edge: .top, spacing: 0) { - VStack(spacing: 0) { - TabBar(windowController: windowController, workspace: workspace) - CustomDivider() - BreadcrumbsView(item, workspace: workspace) - } - } - .safeAreaInset(edge: .bottom) { - StatusBarView() - } - } else { - Text("CodeEdit cannot open this file because its file type is not supported.") - } - } - .onAppear { workspace.openFile(item: item) } + NavigationLink { + WorkspaceCodeFileView(windowController: windowController, + workspace: workspace) + .onAppear { workspace.openFile(item: item) } } label: { Label(item.url.lastPathComponent, systemImage: item.systemImage) .accentColor(iconStyle == .color ? item.iconColor : .secondary) diff --git a/CodeEdit/WorkspaceView.swift b/CodeEdit/WorkspaceView.swift index 796e64877..7c82b36e2 100644 --- a/CodeEdit/WorkspaceView.swift +++ b/CodeEdit/WorkspaceView.swift @@ -30,7 +30,8 @@ struct WorkspaceView: View { SideBar(workspace: workspace, windowController: windowController) .frame(minWidth: 250) - Text("Open file from sidebar") + WorkspaceCodeFileView(windowController: windowController, + workspace: workspace) } else { EmptyView() } diff --git a/CodeEditModules/Modules/CodeFile/src/CodeFile.swift b/CodeEditModules/Modules/CodeFile/src/CodeFile.swift index 82a0fff02..9fb606b3c 100644 --- a/CodeEditModules/Modules/CodeFile/src/CodeFile.swift +++ b/CodeEditModules/Modules/CodeFile/src/CodeFile.swift @@ -58,7 +58,7 @@ public final class CodeFileDocument: NSDocument, ObservableObject { } } -private extension CodeEditor.Language { +public extension CodeEditor.Language { init(url: URL) { var value = url.pathExtension switch value { diff --git a/CodeEditModules/Modules/CodeFile/src/CodeFileView.swift b/CodeEditModules/Modules/CodeFile/src/CodeFileView.swift index 2f708b87c..0f03bc4c9 100644 --- a/CodeEditModules/Modules/CodeFile/src/CodeFileView.swift +++ b/CodeEditModules/Modules/CodeFile/src/CodeFileView.swift @@ -20,18 +20,6 @@ public struct CodeFileView: View { } public var body: some View { - CodeEditor( - source: $codeFile.content, - language: codeFile.fileLanguage(), - theme: getTheme(), - indentStyle: .system - ) + ThemedCodeView($codeFile.content, language: codeFile.fileLanguage()) } - - private func getTheme() -> CodeEditor.ThemeName { - if theme == .atelierSavannaAuto { - return colorScheme == .light ? .atelierSavannaLight : .atelierSavannaDark - } - return theme - } } diff --git a/CodeEditModules/Modules/CodeFile/src/ThemedCodeView.swift b/CodeEditModules/Modules/CodeFile/src/ThemedCodeView.swift new file mode 100644 index 000000000..956ad330d --- /dev/null +++ b/CodeEditModules/Modules/CodeFile/src/ThemedCodeView.swift @@ -0,0 +1,46 @@ +// +// ThemedCodeView.swift +// +// +// Created by Pavel Kasila on 20.03.22. +// + +import SwiftUI +import CodeEditor + +public struct ThemedCodeView: View { + @Binding public var content: String + public var language: CodeEditor.Language + public var editable: Bool + @Environment(\.colorScheme) private var colorScheme + @AppStorage(CodeEditorTheme.storageKey) var theme: CodeEditor.ThemeName = .atelierSavannaAuto + + public init(_ content: Binding, language: CodeEditor.Language, editable: Bool = true) { + self._content = content + self.language = language + self.editable = editable + } + + public var body: some View { + CodeEditor( + source: $content, + language: language, + theme: getTheme(), + flags: editable ? .defaultEditorFlags : .defaultViewerFlags, + indentStyle: .system + ) + } + + private func getTheme() -> CodeEditor.ThemeName { + if theme == .atelierSavannaAuto { + return colorScheme == .light ? .atelierSavannaLight : .atelierSavannaDark + } + return theme + } +} + +struct SwiftUIView_Previews: PreviewProvider { + static var previews: some View { + ThemedCodeView(.constant("## Example"), language: .markdown) + } +} diff --git a/CodeEditModules/Modules/Overlays/src/OverlayPanel.swift b/CodeEditModules/Modules/Overlays/src/OverlayPanel.swift new file mode 100644 index 000000000..a6ab7c789 --- /dev/null +++ b/CodeEditModules/Modules/Overlays/src/OverlayPanel.swift @@ -0,0 +1,33 @@ +// +// OverlayPanel.swift +// CodeEdit +// +// Created by Pavel Kasila on 20.03.22. +// + +import Cocoa + +public class OverlayPanel: NSPanel, NSWindowDelegate { + public init() { + super.init( + contentRect: NSRect(x: 0, y: 0, width: 500, height: 48), + styleMask: [.fullSizeContentView, .titled, .resizable], + backing: .buffered, defer: false) + self.delegate = self + self.center() + self.titlebarAppearsTransparent = true + self.isMovableByWindowBackground = true + } + + public override func standardWindowButton(_ button: NSWindow.ButtonType) -> NSButton? { + let btn = super.standardWindowButton(button) + btn?.isHidden = true + return btn + } + + public func windowDidResignKey(_ notification: Notification) { + if let panel = notification.object as? OverlayPanel { + panel.close() + } + } +} diff --git a/CodeEditModules/Package.swift b/CodeEditModules/Package.swift index fc39fed2f..3a638ab78 100644 --- a/CodeEditModules/Package.swift +++ b/CodeEditModules/Package.swift @@ -24,7 +24,11 @@ let package = Package( .library( name: "StatusBar", targets: ["StatusBar"] - ) + ), + .library( + name: "Overlays", + targets: ["Overlays"] + ) ], dependencies: [ .package( @@ -82,6 +86,10 @@ let package = Package( .target( name: "StatusBar", path: "Modules/StatusBar/src" - ) + ), + .target( + name: "Overlays", + path: "Modules/Overlays/src" + ) ] )