diff --git a/CodeEdit/Documents/WorkspaceCodeFileView.swift b/CodeEdit/Documents/WorkspaceCodeFileView.swift index c131cd1190..62d3b30f7a 100644 --- a/CodeEdit/Documents/WorkspaceCodeFileView.swift +++ b/CodeEdit/Documents/WorkspaceCodeFileView.swift @@ -11,6 +11,7 @@ import WorkspaceClient import StatusBar import Breadcrumbs import AppPreferences +import UniformTypeIdentifiers struct WorkspaceCodeFileView: View { var windowController: NSWindowController @@ -30,26 +31,67 @@ struct WorkspaceCodeFileView: View { } return file.tabID == workspace.selectionState.selectedId }) { - if let codeFile = workspace.selectionState.openedCodeFiles[item] { - CodeFileView(codeFile: codeFile) - .safeAreaInset(edge: .top, spacing: 0) { - VStack(spacing: 0) { - BreadcrumbsView(file: item, tappedOpenFile: workspace.openTab(item:)) - Divider() - } - } - } else { - Text("No Editor") - .font(.system(size: 17)) - .foregroundColor(.secondary) - .frame(minHeight: 0) - .clipped() + if let fileItem = workspace.selectionState.openedCodeFiles[item] { + if fileItem.typeOfFile == .text { + codeFileView(fileItem, for: item) + } else { + otherFileView(fileItem, for: item) + } } + } else { + Text("No Editor") + .font(.system(size: 17)) + .foregroundColor(.secondary) + .frame(minHeight: 0) + .clipped() } } .frame(maxWidth: .infinity, maxHeight: .infinity) } + @ViewBuilder + private func codeFileView( + _ codeFile: CodeFileDocument, + for item: WorkspaceClient.FileItem + ) -> some View { + CodeFileView(codeFile: codeFile) + .safeAreaInset(edge: .top, spacing: 0) { + VStack(spacing: 0) { + BreadcrumbsView(file: item, tappedOpenFile: workspace.openTab(item:)) + Divider() + } + } + } + + @ViewBuilder + private func otherFileView( + _ otherFile: CodeFileDocument, + for item: WorkspaceClient.FileItem + ) -> some View { + ZStack { + if let url = otherFile.previewItemURL, + let image = NSImage(contentsOf: url), + otherFile.typeOfFile == .image { + GeometryReader { proxy in + if image.size.width > proxy.size.width || image.size.height > proxy.size.height { + OtherFileView(otherFile) + } else { + OtherFileView(otherFile) + .frame(width: image.size.width, height: image.size.height) + .position(x: proxy.frame(in: .local).midX, y: proxy.frame(in: .local).midY) + } + } + } else { + OtherFileView(otherFile) + } + }.safeAreaInset(edge: .top, spacing: 0) { + VStack(spacing: 0) { + BreadcrumbsView(file: item, tappedOpenFile: workspace.openTab(item:)) + Divider() + } + } + } + var body: some View { codeView .frame(maxWidth: .infinity, maxHeight: .infinity) diff --git a/CodeEdit/Documents/WorkspaceDocument.swift b/CodeEdit/Documents/WorkspaceDocument.swift index eb1cca78e1..1c49245bc9 100644 --- a/CodeEdit/Documents/WorkspaceDocument.swift +++ b/CodeEdit/Documents/WorkspaceDocument.swift @@ -48,7 +48,9 @@ import TabBar func openTab(item: TabBarItemRepresentable) { do { updateNewlyOpenedTabs(item: item) - + if selectionState.selectedId != item.tabID { + selectionState.selectedId = item.tabID + } switch item.tabID { case .codeEditor: guard let file = item as? WorkspaceClient.FileItem else { return } @@ -58,9 +60,6 @@ import TabBar self.openExtension(item: plugin) } - if selectionState.selectedId != item.tabID { - selectionState.selectedId = item.tabID - } } catch let err { Swift.print(err) } @@ -90,17 +89,16 @@ import TabBar } private func openFile(item: WorkspaceClient.FileItem) throws { + if !selectionState.openFileItems.contains(item) { + selectionState.openFileItems.append(item) + } + let pathExtention = item.url.pathExtension let codeFile = try CodeFileDocument( for: item.url, withContentsOf: item.url, - ofType: "public.source-code" + ofType: pathExtention ) - - if !selectionState.openFileItems.contains(item) { - selectionState.openFileItems.append(item) - - selectionState.openedCodeFiles[item] = codeFile - } + selectionState.openedCodeFiles[item] = codeFile Swift.print("Opening file for item: ", item.url) } diff --git a/CodeEditModules/Modules/CodeFile/src/CodeFile.swift b/CodeEditModules/Modules/CodeFile/src/CodeFile.swift index b97b04f79e..5f04b54e67 100644 --- a/CodeEditModules/Modules/CodeFile/src/CodeFile.swift +++ b/CodeEditModules/Modules/CodeFile/src/CodeFile.swift @@ -8,17 +8,51 @@ import AppKit import Foundation import SwiftUI +import UniformTypeIdentifiers +import QuickLookUI public enum CodeFileError: Error { case failedToDecode case failedToEncode + case fileTypeError } @objc(CodeFileDocument) -public final class CodeFileDocument: NSDocument, ObservableObject { +public final class CodeFileDocument: NSDocument, ObservableObject, QLPreviewItem { + @Published var content = "" + /* + This is the main type of the document. + For example, if the file is end with '.png', it will be an image, + if the file is end with '.py', it will be a text file. + If text content is not empty, return text + If its neither image or text, this could be nil. + */ + public var typeOfFile: UTType? { + if !self.content.isEmpty { + return UTType.text + } + guard let fileType = fileType, let type = UTType(filenameExtension: fileType) else { + return nil + } + if type.conforms(to: UTType.image) { + return UTType.image + } + if type.conforms(to: UTType.text) { + return UTType.text + } + return nil + } + + /* + This is the QLPreviewItemURL + */ + public var previewItemURL: URL? { + fileURL + } + // MARK: - NSDocument override public class var autosavesInPlace: Bool { @@ -44,8 +78,10 @@ public final class CodeFileDocument: NSDocument, ObservableObject { return data } + /// This fuction is used for decoding files. + /// It should not throw error as unsupported files can still be opened by QLPreviewView. override public func read(from data: Data, ofType _: String) throws { - guard let content = String(data: data, encoding: .utf8) else { throw CodeFileError.failedToDecode } + guard let content = String(data: data, encoding: .utf8) else { return } self.content = content } } diff --git a/CodeEditModules/Modules/CodeFile/src/Image/ImageFileView.swift b/CodeEditModules/Modules/CodeFile/src/Image/ImageFileView.swift index 8324d98e4c..e263cfaf6e 100644 --- a/CodeEditModules/Modules/CodeFile/src/Image/ImageFileView.swift +++ b/CodeEditModules/Modules/CodeFile/src/Image/ImageFileView.swift @@ -7,31 +7,28 @@ import SwiftUI -struct ImageFileView: View { - var body: some View { - Image("") - .resizable() - .frame(maxWidth: .infinity, maxHeight: .infinity) - } - - func loadImageFromDiskWith(fileName: String) -> NSImage? { - - let documentDirectory = FileManager.SearchPathDirectory.documentDirectory - let userDomainMask = FileManager.SearchPathDomainMask.userDomainMask - let paths = NSSearchPathForDirectoriesInDomains(documentDirectory, userDomainMask, true) +public struct ImageFileView: View { - if let dirPath = paths.first { - let imageUrl = URL(fileURLWithPath: dirPath).appendingPathComponent(fileName) - let image = NSImage(contentsOfFile: imageUrl.path) - return image + private let image: NSImage? - } - return nil + public init(image: NSImage?) { + self.image = image } -} -struct ImageFileView_Previews: PreviewProvider { - static var previews: some View { - ImageFileView() + public var body: some View { + GeometryReader { proxy in + if let image = image { + if image.size.width > proxy.size.width || image.size.height > proxy.size.height { + Image(nsImage: image) + .resizable() + .scaledToFit() + } else { + Image(nsImage: image) + .frame(width: proxy.size.width, height: proxy.size.height) + } + } else { + EmptyView() + } + } } } diff --git a/CodeEditModules/Modules/CodeFile/src/Other/OtherFileView.swift b/CodeEditModules/Modules/CodeFile/src/Other/OtherFileView.swift new file mode 100644 index 0000000000..1a5c2a9176 --- /dev/null +++ b/CodeEditModules/Modules/CodeFile/src/Other/OtherFileView.swift @@ -0,0 +1,45 @@ +// +// OtherFileView.swift +// +// +// Created by Shibo Tong on 10/7/2022. +// + +import SwiftUI +import QuickLookUI + +/// A SwiftUI Wrapper for `QLPreviewView` +/// Mainly used for other unsupported files +/// ## Usage +/// ```swift +/// OtherFileView(otherFile) +/// ``` +public struct OtherFileView: NSViewRepresentable { + + private var otherFile: CodeFileDocument + + /// Initialize the OtherFileView + /// - Parameter otherFile: a file which contains URL to show preview + public init( + _ otherFile: CodeFileDocument + ) { + self.otherFile = otherFile + } + + public func makeNSView(context: Context) -> QLPreviewView { + let qlPreviewView = QLPreviewView() + if let previewItemURL = otherFile.previewItemURL { + qlPreviewView.previewItem = previewItemURL as QLPreviewItem + } + return qlPreviewView + } + + /// Update preview file when file changed + public func updateNSView(_ nsView: QLPreviewView, context: Context) { + if let previewItemURL = otherFile.previewItemURL { + nsView.previewItem = previewItemURL as QLPreviewItem + } + + } + +}