From 7d73e1d8caff01fa572f1592bfeb00a6160dacb8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Larra=C3=B1aga?= Date: Thu, 5 Sep 2024 00:13:47 -0700 Subject: [PATCH 1/3] Fixed merge conflicts. Fixed Open As Source Code menu item for all text files. --- .../QuickLookPreviewController.swift | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/QuickLookPreviewController.swift diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/QuickLookPreviewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/QuickLookPreviewController.swift new file mode 100644 index 000000000..6cd28f57c --- /dev/null +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/QuickLookPreviewController.swift @@ -0,0 +1,59 @@ +// +// QuickLookPreviewController.swift +// CodeEdit +// +// Created by Leonardo LarraƱaga on 7/10/24. +// + +import QuickLookUI +import SwiftUI + +/// A class that handles file preview using Quick Look. +class QuickLookPreviewController: NSObject, QLPreviewPanelDataSource, QLPreviewPanelDelegate { + /// Shared instance of `QuickLookPreviewController` for global use. + static let shared = QuickLookPreviewController() + + /// URL of the item to preview. + static var previewItemURL: URL! + + /// Returns the number of items to preview in the Quick Look panel. + /// Requiered function for `QLPreviewPanelDelegate`. + func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int { + return 1 + } + + /// Returns the item to preview at the specified index. + /// Requiered function for `QLPreviewPanelDelegate`. + func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> (any QLPreviewItem)! { + return QuickLookPreviewController.previewItemURL as QLPreviewItem + } + + /// Opens the item in a Quick Look tab. + @objc + func openQuickLookTab(_ sender: NSMenuItem) { + guard let context = sender.representedObject as? (CEWorkspaceFile, WorkspaceDocument?) else { return } + let (item, workspace) = context + + guard let workspace, + let editorManager = workspace.editorManager else { return } + item.isOpeningInQuickLook = true + editorManager.openTab(item: item) + } + + /// Creates a Quick Look menu for the specified item. + /// - Parameter item: The workspace file (`CEWorkspaceFile`) for which the menu item will be created. + /// - Returns: A menu item configured to open Quick Look. + static func quickLookMenu(item: CEWorkspaceFile, workspace: WorkspaceDocument?) -> NSMenuItem { + QuickLookPreviewController.previewItemURL = item.url + + let quickLookMenuItem = NSMenuItem( + title: "Quick Look", + action: #selector(QuickLookPreviewController.shared.openQuickLookTab(_:)), + keyEquivalent: "" + ) + quickLookMenuItem.target = QuickLookPreviewController.shared + quickLookMenuItem.representedObject = (item, workspace) + + return quickLookMenuItem + } +} From 412dc74f6a38c3da4eba8582f18ade7275235bc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Leonardo=20Larra=C3=B1aga?= Date: Thu, 5 Sep 2024 00:13:47 -0700 Subject: [PATCH 2/3] Fixed merge conflicts. Fixed Open As Source Code menu item for all text files. --- CodeEdit.xcodeproj/project.pbxproj | 6 +- .../xcshareddata/swiftpm/Package.resolved | 4 +- .../CEWorkspace/Models/CEWorkspaceFile.swift | 3 + .../Editor/Views/EditorAreaFileView.swift | 7 ++- .../OutlineView/ProjectNavigatorMenu.swift | 45 ++++++++++++-- .../QuickLookPreviewController.swift | 59 +++++++++++++++++++ 6 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/QuickLookPreviewController.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 2a2bd8d04..070ac20d8 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -100,6 +100,7 @@ 30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */; }; 3E0196732A3921AC002648D8 /* codeedit_shell_integration_rc.zsh in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196722A3921AC002648D8 /* codeedit_shell_integration_rc.zsh */; }; 3E01967A2A392B45002648D8 /* codeedit_shell_integration.bash in Resources */ = {isa = PBXBuildFile; fileRef = 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */; }; + 4ACC3CF72C898EA700380885 /* QuickLookPreviewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4ACC3CF62C898EA700380885 /* QuickLookPreviewController.swift */; }; 4E7F066629602E7B00BB3C12 /* CodeEditSplitViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */; }; 4EE96ECB2960565E00FFBEA8 /* DocumentsUnitTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE96ECA2960565E00FFBEA8 /* DocumentsUnitTests.swift */; }; 4EE96ECE296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE96ECD296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift */; }; @@ -760,6 +761,7 @@ 30E6D0002A6E505200A58B20 /* NavigatorSidebarViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigatorSidebarViewModel.swift; sourceTree = ""; }; 3E0196722A3921AC002648D8 /* codeedit_shell_integration_rc.zsh */ = {isa = PBXFileReference; lastKnownFileType = text.script.sh; path = codeedit_shell_integration_rc.zsh; sourceTree = ""; }; 3E0196792A392B45002648D8 /* codeedit_shell_integration.bash */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = codeedit_shell_integration.bash; sourceTree = ""; }; + 4ACC3CF62C898EA700380885 /* QuickLookPreviewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickLookPreviewController.swift; sourceTree = ""; }; 4E7F066529602E7B00BB3C12 /* CodeEditSplitViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeEditSplitViewController.swift; sourceTree = ""; }; 4EE96ECA2960565E00FFBEA8 /* DocumentsUnitTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DocumentsUnitTests.swift; sourceTree = ""; }; 4EE96ECD296059E000FFBEA8 /* NSHapticFeedbackPerformerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSHapticFeedbackPerformerMock.swift; sourceTree = ""; }; @@ -1423,6 +1425,7 @@ 285FEC6C27FE4AC700E57D53 /* OutlineView */ = { isa = PBXGroup; children = ( + 4ACC3CF62C898EA700380885 /* QuickLookPreviewController.swift */, 2847019D27FDDF7600F87B6B /* ProjectNavigatorOutlineView.swift */, 285FEC6D27FE4B4A00E57D53 /* ProjectNavigatorViewController.swift */, 6CC17B522C43314000834E2C /* ProjectNavigatorViewController+NSOutlineViewDelegate.swift */, @@ -4107,6 +4110,7 @@ B62AEDDC2A27C1B3009A9F52 /* OSLogType+Color.swift in Sources */, 587B9E6329301D8F00AC7927 /* GitLabAccount.swift in Sources */, 6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */, + 4ACC3CF72C898EA700380885 /* QuickLookPreviewController.swift in Sources */, 285FEC7027FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift in Sources */, B69D3EE32C5F536B005CF43A /* ActiveTaskView.swift in Sources */, 300051672BBD3A5D00A98562 /* ServiceContainer.swift in Sources */, @@ -5541,7 +5545,7 @@ repositoryURL = "https://github.com/groue/GRDB.swift.git"; requirement = { kind = upToNextMajorVersion; - minimumVersion = 5.2.0; + minimumVersion = 6.29.2; }; }; 6C6BD6F229CD142C00235D17 /* XCRemoteSwiftPackageReference "collectionconcurrencykit" */ = { diff --git a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a97c083c2..e2feb3fe3 100644 --- a/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/CodeEdit.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -96,8 +96,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/groue/GRDB.swift.git", "state" : { - "revision" : "dd7e7f39e8e4d7a22d258d9809a882f914690b01", - "version" : "5.26.1" + "revision" : "57a4587b5b09ac706bc985288a62432ddbf20b09", + "version" : "6.29.2" } }, { diff --git a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift index 818578965..1a10ac841 100644 --- a/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift +++ b/CodeEdit/Features/CEWorkspace/Models/CEWorkspaceFile.swift @@ -161,6 +161,9 @@ final class CEWorkspaceFile: Codable, Comparable, Hashable, Identifiable, Editor FileIcon.iconColor(fileType: type) } + /// Returns whether the item is being opened in Quick Look. + var isOpeningInQuickLook = false + init( url: URL, changeType: GitStatus? = nil, diff --git a/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift b/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift index 7189a7968..6ee36e99c 100644 --- a/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift +++ b/CodeEdit/Features/Editor/Views/EditorAreaFileView.swift @@ -26,8 +26,11 @@ struct EditorAreaFileView: View { @ViewBuilder var editorAreaFileView: some View { if let document = file.fileDocument { - - if let utType = document.utType, utType.conforms(to: .text) { + if file.isOpeningInQuickLook { + AnyFileView(file.url) + .padding(.top, edgeInsets.top - 1.74) + .padding(.bottom, StatusBarView.height + 1.26) + } else if let utType = document.utType, utType.conforms(to: .text) { CodeFileView(codeFile: document, textViewCoordinators: textViewCoordinators) } else { NonTextFileView(fileDocument: document) diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift index 9841859c1..2010cd001 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift @@ -120,8 +120,9 @@ final class ProjectNavigatorMenu: NSMenu { return ([.none()], []) } var primaryItems = [NSMenuItem]() - if type.conforms(to: .sourceCode) { - primaryItems.append(.sourceCode()) + if type.conforms(to: .text) || checkFileIsText(for: item) { + primaryItems.append(menuItem("Source Code", action: #selector(openInSourceCode))) + primaryItems.append(.separator()) } if type.conforms(to: .propertyList) { primaryItems.append(.propertyList()) @@ -141,10 +142,7 @@ final class ProjectNavigatorMenu: NSMenu { secondaryItems.append(.hex()) } - // FIXME: Update the quickLook condition - if type.conforms(to: .data) { - secondaryItems.append(.quickLook()) - } + secondaryItems.append(QuickLookPreviewController.quickLookMenu(item: item, workspace: workspace)) return (primaryItems, secondaryItems) } @@ -178,6 +176,31 @@ final class ProjectNavigatorMenu: NSMenu { return sourceControlMenu } + /// Checks if the given `CEWorkspaceFile` is a text file. + /// This function verifies if the content of the file can be decoded as text. + /// It is used as a fallback when the type identifier does not conform to the expected text type. + func checkFileIsText(for item: CEWorkspaceFile) -> Bool { + do { + // Read the first 512 bytes of the file. + let fileHandle = try FileHandle(forReadingFrom: item.url) + let fileData = fileHandle.readData(ofLength: 512) + defer { fileHandle.closeFile() } + + // Extreme use case: Returns true for empty files. + // E.g. The user hasn't typed anything on it, yet. + // This used for files like .env, .gitignore or files that lack an extension. + if fileData.isEmpty { + return true + } + + // swiftlint:disable:next non_optional_string_data_conversion + return String(data: fileData, encoding: .utf8) != nil + } catch { + print("Error reading file: \(error)") + return false + } + } + /// Updates the menu for the selected item and hides it if no item is provided. override func update() { removeAllItems() @@ -258,6 +281,16 @@ final class ProjectNavigatorMenu: NSMenu { guard let item else { return } workspace?.workspaceFileManager?.duplicate(file: item) } + + /// Action that opens the file in Source Code. + @objc + private func openInSourceCode() { + guard let item else { return } + item.isOpeningInQuickLook = false + guard let workspace, + let editorManager = workspace.editorManager else { return } + editorManager.openTab(item: item) + } } extension NSMenuItem { diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/QuickLookPreviewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/QuickLookPreviewController.swift new file mode 100644 index 000000000..6cd28f57c --- /dev/null +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/QuickLookPreviewController.swift @@ -0,0 +1,59 @@ +// +// QuickLookPreviewController.swift +// CodeEdit +// +// Created by Leonardo LarraƱaga on 7/10/24. +// + +import QuickLookUI +import SwiftUI + +/// A class that handles file preview using Quick Look. +class QuickLookPreviewController: NSObject, QLPreviewPanelDataSource, QLPreviewPanelDelegate { + /// Shared instance of `QuickLookPreviewController` for global use. + static let shared = QuickLookPreviewController() + + /// URL of the item to preview. + static var previewItemURL: URL! + + /// Returns the number of items to preview in the Quick Look panel. + /// Requiered function for `QLPreviewPanelDelegate`. + func numberOfPreviewItems(in panel: QLPreviewPanel!) -> Int { + return 1 + } + + /// Returns the item to preview at the specified index. + /// Requiered function for `QLPreviewPanelDelegate`. + func previewPanel(_ panel: QLPreviewPanel!, previewItemAt index: Int) -> (any QLPreviewItem)! { + return QuickLookPreviewController.previewItemURL as QLPreviewItem + } + + /// Opens the item in a Quick Look tab. + @objc + func openQuickLookTab(_ sender: NSMenuItem) { + guard let context = sender.representedObject as? (CEWorkspaceFile, WorkspaceDocument?) else { return } + let (item, workspace) = context + + guard let workspace, + let editorManager = workspace.editorManager else { return } + item.isOpeningInQuickLook = true + editorManager.openTab(item: item) + } + + /// Creates a Quick Look menu for the specified item. + /// - Parameter item: The workspace file (`CEWorkspaceFile`) for which the menu item will be created. + /// - Returns: A menu item configured to open Quick Look. + static func quickLookMenu(item: CEWorkspaceFile, workspace: WorkspaceDocument?) -> NSMenuItem { + QuickLookPreviewController.previewItemURL = item.url + + let quickLookMenuItem = NSMenuItem( + title: "Quick Look", + action: #selector(QuickLookPreviewController.shared.openQuickLookTab(_:)), + keyEquivalent: "" + ) + quickLookMenuItem.target = QuickLookPreviewController.shared + quickLookMenuItem.representedObject = (item, workspace) + + return quickLookMenuItem + } +} From 60ea04ac4e4b57a42fe5b91e9b79d64985787998 Mon Sep 17 00:00:00 2001 From: Leonardo <83844690+LeonardoLarranaga@users.noreply.github.com> Date: Thu, 5 Sep 2024 00:22:22 -0700 Subject: [PATCH 3/3] Update - Fixed typo ProjectNavigatorMenu.swift --- .../ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift index 2010cd001..9d087ed52 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorMenu.swift @@ -188,7 +188,7 @@ final class ProjectNavigatorMenu: NSMenu { // Extreme use case: Returns true for empty files. // E.g. The user hasn't typed anything on it, yet. - // This used for files like .env, .gitignore or files that lack an extension. + // This is used for files like .env, .gitignore or files that lack an extension. if fileData.isEmpty { return true }