From bc80af8233dce92c89babcb42286bf558de17788 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Sun, 14 Jul 2024 15:50:11 -0500 Subject: [PATCH 1/6] Appcast Link To "Raw" Release Notes --- .github/workflows/pre-release.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 989e176d9d..14d91d9e41 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -117,13 +117,18 @@ jobs: # SPARKLE_CHANNEL: Seperate dev builds from default channel, to be specified in [SPUUpdaterDelegate allowedChannelsForUpdater:] # SPARKLE_DL_PREFIX: Prefix for the URL from where updates will be downloaded # SPARKLE_LINK: CodeEdit Website - # https://github.com/CodeEditApp/CodeEdit/releases/download/0.0.1-alpha.11/CodeEdit-9113dc5.dmg + # https://github.com/CodeEditApp/CodeEdit/releases/download/0.0.1-alpha.11/CodeEdit-9113dc5.dmg + # RELEASE_NOTES_PREFIX: The URL to prefix before an update link: + # https://codeedit.app/whats-new/raw/{v0.1.0} -- data in {} is inserted by sparkle + # RELEASE_NOTES_URL: The URL of the entire release notes page: https://codeedit.app/whats-new SPARKLE_KEY: ${{ secrets.SPARKLE_KEY }} SPARKLE_CHANNEL: dev SPARKLE_DL_PREFIX: "https://github.com/CodeEditApp/CodeEdit/releases/download" SPARKLE_LINK: "https://github.com/CodeEditApp/CodeEdit" APP_VERSION: ${{ env.APP_VERSION }} APP_BUILD: ${{ env.APP_BUILD }} + RELEASE_NOTES_URL: "https://codeedit.app/whats-new/" + RELEASE_NOTES_PREFIX: "https://codeedit.app/whats-new/raw/" run: | SPARKLE_BIN="$RUNNER_TEMP/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin" SPARKLE_ARCHIVE="$RUNNER_TEMP/Sparkle_Archive" @@ -131,7 +136,7 @@ jobs: mkdir "$SPARKLE_ARCHIVE" cp "$RUNNER_TEMP/CodeEdit.dmg" "$SPARKLE_ARCHIVE" SPARKLE_SIG=$("$SPARKLE_BIN/sign_update" --ed-key-file "$RUNNER_TEMP/sparkle_key" "$SPARKLE_ARCHIVE/CodeEdit.dmg" | cut -d\" -f2) - "$SPARKLE_BIN/generate_appcast" --ed-key-file "$RUNNER_TEMP/sparkle_key" --download-url-prefix "${{ env.SPARKLE_DL_PREFIX }}/v${{ env.APP_VERSION }}/" --link "$SPARKLE_LINK" --channel "$SPARKLE_CHANNEL" --maximum-deltas 0 "$SPARKLE_ARCHIVE" + "$SPARKLE_BIN/generate_appcast" --ed-key-file "$RUNNER_TEMP/sparkle_key" --download-url-prefix "${{ env.SPARKLE_DL_PREFIX }}/v${{ env.APP_VERSION }}/" --link "$SPARKLE_LINK" --channel "$SPARKLE_CHANNEL" --maximum-deltas 0 "$SPARKLE_ARCHIVE" --release-notes-url-prefix "$RELEASE_NOTES_PREFIX" --full-release-notes-url "$RELEASE_NOTES_URL" ############################ # Publish Pre Release From 9483c4e71df03c00b617103d56a1341a250a7dfd Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Sun, 14 Jul 2024 16:48:49 -0500 Subject: [PATCH 2/6] Create Website Deploy Action --- .github/workflows/CI-release-notes.yml | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 .github/workflows/CI-release-notes.yml diff --git a/.github/workflows/CI-release-notes.yml b/.github/workflows/CI-release-notes.yml new file mode 100644 index 0000000000..4a3ced4a07 --- /dev/null +++ b/.github/workflows/CI-release-notes.yml @@ -0,0 +1,11 @@ +on: + release: + types: [created, edited, deleted] + +jobs: + deploy-vercel: + name: Trigger A Deploy On Vercel + runs-on: [self-hosted, macOS] + steps: + - name: Make Curl Request + run: curl -X POST ${{ secrets.VERCEL_DEPLOY_URL }} From 7c450f41fb5bb3986e1534e900078bb2ce9deb60 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Sun, 14 Jul 2024 19:21:55 -0500 Subject: [PATCH 3/6] Make Linter Happy --- CodeEdit.xcodeproj/project.pbxproj | 8 + ...ctNavigatorViewController+DataSource.swift | 119 ++++++++ ...jectNavigatorViewController+Delegate.swift | 148 ++++++++++ .../ProjectNavigatorViewController.swift | 260 +----------------- 4 files changed, 278 insertions(+), 257 deletions(-) create mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+DataSource.swift create mode 100644 CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+Delegate.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 822b617194..c55c447908 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -384,6 +384,8 @@ 6C5FDF7A29E6160000BC08C0 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C5FDF7929E6160000BC08C0 /* AppSettings.swift */; }; 6C6362D42C3E321A0025570D /* Editor+History.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6362D32C3E321A0025570D /* Editor+History.swift */; }; 6C66C31329D05CDC00DE9ED2 /* GRDB in Frameworks */ = {isa = PBXBuildFile; productRef = 6C66C31229D05CDC00DE9ED2 /* GRDB */; }; + 6C67413E2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */; }; + 6C6741402C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */; }; 6C6BD6EF29CD12E900235D17 /* ExtensionManagerWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */; }; 6C6BD6F129CD13FA00235D17 /* ExtensionDiscovery.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */; }; 6C6BD6F429CD142C00235D17 /* CollectionConcurrencyKit in Frameworks */ = {isa = PBXBuildFile; productRef = 6C6BD6F329CD142C00235D17 /* CollectionConcurrencyKit */; }; @@ -1007,6 +1009,8 @@ 6C5C891A2A3F736500A94FE1 /* FocusedValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedValues.swift; sourceTree = ""; }; 6C5FDF7929E6160000BC08C0 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; 6C6362D32C3E321A0025570D /* Editor+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Editor+History.swift"; sourceTree = ""; }; + 6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+DataSource.swift"; sourceTree = ""; }; + 6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+Delegate.swift"; sourceTree = ""; }; 6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionManagerWindow.swift; sourceTree = ""; }; 6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDiscovery.swift; sourceTree = ""; }; 6C6BD6F529CD145F00235D17 /* ExtensionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInfo.swift; sourceTree = ""; }; @@ -1368,6 +1372,8 @@ children = ( 2847019D27FDDF7600F87B6B /* ProjectNavigatorOutlineView.swift */, 285FEC6D27FE4B4A00E57D53 /* ProjectNavigatorViewController.swift */, + 6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */, + 6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */, 285FEC6F27FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift */, 285FEC7127FE4EEF00E57D53 /* ProjectNavigatorMenu.swift */, D7DC4B75298FFBE900D6C83D /* ProjectNavigatorViewController+OutlineTableViewCellDelegate.swift */, @@ -3709,6 +3715,7 @@ 58798237292E30B90085B254 /* FeedbackView.swift in Sources */, 587B9E9829301D8F00AC7927 /* GitCommit.swift in Sources */, 6C5228B529A868BD00AC48F6 /* Environment+ContentInsets.swift in Sources */, + 6C67413E2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift in Sources */, 587B9E9429301D8F00AC7927 /* BitBucketTokenConfiguration.swift in Sources */, 04BA7C222AE2D95E00584E1C /* GitClient+CommitHistory.swift in Sources */, 581BFB672926431000D251EC /* WelcomeWindowView.swift in Sources */, @@ -4062,6 +4069,7 @@ 77A01E302BB4270F00F0EA38 /* ProjectCEWorkspaceSettingsView.swift in Sources */, 669A50532C380C8E00304CD8 /* Collection+subscript_safe.swift in Sources */, 77A01E2C2BB425B200F0EA38 /* CEWorkspaceSettingsData+TasksSettings.swift in Sources */, + 6C6741402C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift in Sources */, 5B241BF32B6DDBFF0016E616 /* IgnorePatternListItemView.swift in Sources */, 6CB52DC92AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift in Sources */, 58F2EB0B292FB2B0004A9BDE /* AccountsSettings.swift in Sources */, diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+DataSource.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+DataSource.swift new file mode 100644 index 0000000000..1437f9c877 --- /dev/null +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+DataSource.swift @@ -0,0 +1,119 @@ +// +// ProjectNavigatorViewController+DataSource.swift +// CodeEdit +// +// Created by Khan Winter on 7/14/24. +// + +import AppKit + +extension ProjectNavigatorViewController: NSOutlineViewDataSource { + func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { + if let item = item as? CEWorkspaceFile { + return item.isFolder ? workspace?.workspaceFileManager?.childrenOfFile(item)?.count ?? 0 : 0 + } + return content.count + } + + func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { + if let item = item as? CEWorkspaceFile, + let children = workspace?.workspaceFileManager?.childrenOfFile(item) { + return children[index] + } + return content[index] + } + + func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { + if let item = item as? CEWorkspaceFile { + return item.isFolder + } + return false + } + + /// write dragged file(s) to pasteboard + func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? { + guard let fileItem = item as? CEWorkspaceFile else { return nil } + return fileItem.url as NSURL + } + + /// declare valid drop target + func outlineView( + _ outlineView: NSOutlineView, + validateDrop info: NSDraggingInfo, + proposedItem item: Any?, + proposedChildIndex index: Int + ) -> NSDragOperation { + guard let fileItem = item as? CEWorkspaceFile else { return [] } + // -1 index indicates that we are hovering over a row in outline view (folder or file) + if index == -1 { + if !fileItem.isFolder { + outlineView.setDropItem(fileItem.parent, dropChildIndex: index) + } + return info.draggingSourceOperationMask == .copy ? .copy : .move + } + return [] + } + + /// handle successful or unsuccessful drop + func outlineView( + _ outlineView: NSOutlineView, + acceptDrop info: NSDraggingInfo, + item: Any?, + childIndex index: Int + ) -> Bool { + guard let pasteboardItems = info.draggingPasteboard.readObjects(forClasses: [NSURL.self]) else { return false } + let fileItemURLS = pasteboardItems.compactMap { $0 as? URL } + + guard let fileItemDestination = item as? CEWorkspaceFile else { return false } + let destParentURL = fileItemDestination.url + + for fileItemURL in fileItemURLS { + let destURL = destParentURL.appendingPathComponent(fileItemURL.lastPathComponent) + // cancel dropping file item on self or in parent directory + if fileItemURL == destURL || fileItemURL == destParentURL { + return false + } + + // Needs to come before call to .removeItem or else race condition occurs + var srcFileItem: CEWorkspaceFile? = workspace?.workspaceFileManager?.getFile(fileItemURL.path) + // If srcFileItem is nil, fileItemUrl is an external file url. + if srcFileItem == nil { + srcFileItem = CEWorkspaceFile(url: URL(fileURLWithPath: fileItemURL.path)) + } + + guard let srcFileItem else { + return false + } + + if CEWorkspaceFile.fileManager.fileExists(atPath: destURL.path) { + let shouldReplace = replaceFileDialog(fileName: fileItemURL.lastPathComponent) + guard shouldReplace else { + return false + } + do { + try CEWorkspaceFile.fileManager.removeItem(at: destURL) + } catch { + fatalError(error.localizedDescription) + } + } + if info.draggingSourceOperationMask == .copy { + self.copyFile(file: srcFileItem, to: destURL) + } else { + self.moveFile(file: srcFileItem, to: destURL) + } + } + return true + } + + func replaceFileDialog(fileName: String) -> Bool { + let alert = NSAlert() + alert.messageText = """ + A file or folder with the name \(fileName) already exists in the destination folder. Do you want to replace it? + """ + alert.informativeText = "This action is irreversible!" + alert.alertStyle = .warning + alert.addButton(withTitle: "Replace") + alert.addButton(withTitle: "Cancel") + return alert.runModal() == .alertFirstButtonReturn + } +} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+Delegate.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+Delegate.swift new file mode 100644 index 0000000000..3389db81c6 --- /dev/null +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController+Delegate.swift @@ -0,0 +1,148 @@ +// +// ProjectNavigatorViewController+Delegate.swift +// CodeEdit +// +// Created by Khan Winter on 7/14/24. +// + +import AppKit + +extension ProjectNavigatorViewController: NSOutlineViewDelegate { + func outlineView( + _ outlineView: NSOutlineView, + shouldShowCellExpansionFor tableColumn: NSTableColumn?, + item: Any + ) -> Bool { + true + } + + func outlineView(_ outlineView: NSOutlineView, shouldShowOutlineCellForItem item: Any) -> Bool { + true + } + + func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { + guard let tableColumn else { return nil } + + let frameRect = NSRect(x: 0, y: 0, width: tableColumn.width, height: rowHeight) + + return ProjectNavigatorTableViewCell(frame: frameRect, item: item as? CEWorkspaceFile, delegate: self) + } + + func outlineViewSelectionDidChange(_ notification: Notification) { + guard let outlineView = notification.object as? NSOutlineView else { return } + + let selectedIndex = outlineView.selectedRow + + guard let item = outlineView.item(atRow: selectedIndex) as? CEWorkspaceFile else { return } + + if !item.isFolder && shouldSendSelectionUpdate { + DispatchQueue.main.async { + self.shouldSendSelectionUpdate = false + self.workspace?.editorManager.activeEditor.openTab(file: item, asTemporary: true) + self.shouldSendSelectionUpdate = true + } + } + } + + func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { + rowHeight // This can be changed to 20 to match Xcode's row height. + } + + func outlineViewItemDidExpand(_ notification: Notification) { + guard let id = workspace?.editorManager.activeEditor.selectedTab?.file.id, + let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true), + /// update outline selection only if the parent of selected item match with expanded item + item.parent === notification.userInfo?["NSObject"] as? CEWorkspaceFile else { + return + } + /// select active file under collapsed folder only if its parent is expanding + if outlineView.isItemExpanded(item.parent) { + updateSelection(itemID: item.id) + } + } + + func outlineViewItemDidCollapse(_ notification: Notification) {} + + func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? { + guard let id = object as? CEWorkspaceFile.ID, + let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true) else { return nil } + return item + } + + func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? { + guard let item = item as? CEWorkspaceFile else { return nil } + return item.id + } + + /// Finds and selects an ``Item`` from an array of ``Item`` and their `children` based on the `id`. + /// - Parameters: + /// - id: the id of the item item + /// - collection: the array to search for + /// - forcesReveal: The boolean to indicates whether or not it should force to reveal the selected file. + func select(by id: EditorTabID, forcesReveal: Bool) { + guard case .codeEditor(let path) = id, + let item = workspace?.workspaceFileManager?.getFile(path, createIfNotFound: true) else { + return + } + // If the user has set "Reveal file on selection change" to on or it is forced to reveal, + // we need to reveal the item before selecting the row. + if Settings.shared.preferences.general.revealFileOnFocusChange || forcesReveal { + reveal(item) + } + let row = outlineView.row(forItem: item) + if row == -1 { + outlineView.deselectRow(outlineView.selectedRow) + } + shouldSendSelectionUpdate = false + outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false) + shouldSendSelectionUpdate = true + } + + /// Reveals the given `fileItem` in the outline view by expanding all the parent directories of the file. + /// If the file is not found, it will present an alert saying so. + /// - Parameter fileItem: The file to reveal. + public func reveal(_ fileItem: CEWorkspaceFile) { + if let parent = fileItem.parent { + expandParent(item: parent) + } + let row = outlineView.row(forItem: fileItem) + shouldSendSelectionUpdate = false + outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false) + shouldSendSelectionUpdate = true + + if row < 0 { + let alert = NSAlert() + alert.messageText = NSLocalizedString( + "Could not find file", + comment: "Could not find file" + ) + alert.runModal() + return + } else { + let visibleRect = scrollView.contentView.visibleRect + let visibleRows = outlineView.rows(in: visibleRect) + guard !visibleRows.contains(row) else { + /// in case that the selected file is not fully visible (some parts are out of the visible rect), + /// `scrollRowToVisible(_:)` method brings the file where it can be fully visible. + outlineView.scrollRowToVisible(row) + return + } + let rowRect = outlineView.rect(ofRow: row) + let centerY = rowRect.midY - (visibleRect.height / 2) + let center = NSPoint(x: 0, y: centerY) + /// `scroll(_:)` method alone doesn't bring the selected file to the center in some cases. + /// calling `scrollRowToVisible(_:)` method before it makes the file reveal in the center more correctly. + outlineView.scrollRowToVisible(row) + outlineView.scroll(center) + } + } + + /// Method for recursively expanding a file's parent directories. + /// - Parameter item: + private func expandParent(item: CEWorkspaceFile) { + if let parent = item.parent as CEWorkspaceFile? { + expandParent(item: parent) + } + outlineView.expandItem(item) + } +} diff --git a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift index 480e81a1d4..89d58212ae 100644 --- a/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift +++ b/CodeEdit/Features/NavigatorArea/ProjectNavigator/OutlineView/ProjectNavigatorViewController.swift @@ -20,7 +20,7 @@ final class ProjectNavigatorViewController: NSViewController { /// Gets the folder structure /// /// Also creates a top level item "root" which represents the projects root directory and automatically expands it. - private var content: [CEWorkspaceFile] { + var content: [CEWorkspaceFile] { guard let folderURL = workspace?.workspaceFileManager?.folderUrl else { return [] } guard let root = workspace?.workspaceFileManager?.getFile(folderURL.path) else { return [] } return [root] @@ -52,7 +52,7 @@ final class ProjectNavigatorViewController: NSViewController { /// This helps determine whether or not to send an `openTab` when the selection changes. /// Used b/c the state may update when the selection changes, but we don't necessarily want /// to open the file a second time. - private var shouldSendSelectionUpdate: Bool = true + var shouldSendSelectionUpdate: Bool = true /// Setup the ``scrollView`` and ``outlineView`` override func loadView() { @@ -115,7 +115,7 @@ final class ProjectNavigatorViewController: NSViewController { outlineView.deselectRow(outlineView.selectedRow) return } - select(by: .codeEditor(itemID), forcesReveal: forcesReveal) + self.select(by: .codeEditor(itemID), forcesReveal: forcesReveal) } /// Expand or collapse the folder on double click @@ -147,257 +147,3 @@ final class ProjectNavigatorViewController: NSViewController { // TODO: File filtering } - -// MARK: - NSOutlineViewDataSource - -extension ProjectNavigatorViewController: NSOutlineViewDataSource { - func outlineView(_ outlineView: NSOutlineView, numberOfChildrenOfItem item: Any?) -> Int { - if let item = item as? CEWorkspaceFile { - return item.isFolder ? workspace?.workspaceFileManager?.childrenOfFile(item)?.count ?? 0 : 0 - } - return content.count - } - - func outlineView(_ outlineView: NSOutlineView, child index: Int, ofItem item: Any?) -> Any { - if let item = item as? CEWorkspaceFile, - let children = workspace?.workspaceFileManager?.childrenOfFile(item) { - return children[index] - } - return content[index] - } - - func outlineView(_ outlineView: NSOutlineView, isItemExpandable item: Any) -> Bool { - if let item = item as? CEWorkspaceFile { - return item.isFolder - } - return false - } - - /// write dragged file(s) to pasteboard - func outlineView(_ outlineView: NSOutlineView, pasteboardWriterForItem item: Any) -> NSPasteboardWriting? { - guard let fileItem = item as? CEWorkspaceFile else { return nil } - return fileItem.url as NSURL - } - - /// declare valid drop target - func outlineView( - _ outlineView: NSOutlineView, - validateDrop info: NSDraggingInfo, - proposedItem item: Any?, - proposedChildIndex index: Int - ) -> NSDragOperation { - guard let fileItem = item as? CEWorkspaceFile else { return [] } - // -1 index indicates that we are hovering over a row in outline view (folder or file) - if index == -1 { - if !fileItem.isFolder { - outlineView.setDropItem(fileItem.parent, dropChildIndex: index) - } - return info.draggingSourceOperationMask == .copy ? .copy : .move - } - return [] - } - - /// handle successful or unsuccessful drop - func outlineView( - _ outlineView: NSOutlineView, - acceptDrop info: NSDraggingInfo, - item: Any?, - childIndex index: Int - ) -> Bool { - guard let pasteboardItems = info.draggingPasteboard.readObjects(forClasses: [NSURL.self]) else { return false } - let fileItemURLS = pasteboardItems.compactMap { $0 as? URL } - - guard let fileItemDestination = item as? CEWorkspaceFile else { return false } - let destParentURL = fileItemDestination.url - - for fileItemURL in fileItemURLS { - let destURL = destParentURL.appendingPathComponent(fileItemURL.lastPathComponent) - // cancel dropping file item on self or in parent directory - if fileItemURL == destURL || fileItemURL == destParentURL { - return false - } - - // Needs to come before call to .removeItem or else race condition occurs - var srcFileItem: CEWorkspaceFile? = workspace?.workspaceFileManager?.getFile(fileItemURL.path) - // If srcFileItem is nil, fileItemUrl is an external file url. - if srcFileItem == nil { - srcFileItem = CEWorkspaceFile(url: URL(fileURLWithPath: fileItemURL.path)) - } - - guard let srcFileItem else { - return false - } - - if CEWorkspaceFile.fileManager.fileExists(atPath: destURL.path) { - let shouldReplace = replaceFileDialog(fileName: fileItemURL.lastPathComponent) - guard shouldReplace else { - return false - } - do { - try CEWorkspaceFile.fileManager.removeItem(at: destURL) - } catch { - fatalError(error.localizedDescription) - } - } - if info.draggingSourceOperationMask == .copy { - self.copyFile(file: srcFileItem, to: destURL) - } else { - self.moveFile(file: srcFileItem, to: destURL) - } - } - return true - } - - func replaceFileDialog(fileName: String) -> Bool { - let alert = NSAlert() - alert.messageText = """ - A file or folder with the name \(fileName) already exists in the destination folder. Do you want to replace it? - """ - alert.informativeText = "This action is irreversible!" - alert.alertStyle = .warning - alert.addButton(withTitle: "Replace") - alert.addButton(withTitle: "Cancel") - return alert.runModal() == .alertFirstButtonReturn - } -} - -// MARK: - NSOutlineViewDelegate -extension ProjectNavigatorViewController: NSOutlineViewDelegate { - func outlineView( - _ outlineView: NSOutlineView, - shouldShowCellExpansionFor tableColumn: NSTableColumn?, - item: Any - ) -> Bool { - true - } - - func outlineView(_ outlineView: NSOutlineView, shouldShowOutlineCellForItem item: Any) -> Bool { - true - } - - func outlineView(_ outlineView: NSOutlineView, viewFor tableColumn: NSTableColumn?, item: Any) -> NSView? { - guard let tableColumn else { return nil } - - let frameRect = NSRect(x: 0, y: 0, width: tableColumn.width, height: rowHeight) - - return ProjectNavigatorTableViewCell(frame: frameRect, item: item as? CEWorkspaceFile, delegate: self) - } - - func outlineViewSelectionDidChange(_ notification: Notification) { - guard let outlineView = notification.object as? NSOutlineView else { return } - - let selectedIndex = outlineView.selectedRow - - guard let item = outlineView.item(atRow: selectedIndex) as? CEWorkspaceFile else { return } - - if !item.isFolder && shouldSendSelectionUpdate { - DispatchQueue.main.async { - self.shouldSendSelectionUpdate = false - self.workspace?.editorManager.activeEditor.openTab(file: item, asTemporary: true) - self.shouldSendSelectionUpdate = true - } - } - } - - func outlineView(_ outlineView: NSOutlineView, heightOfRowByItem item: Any) -> CGFloat { - rowHeight // This can be changed to 20 to match Xcode's row height. - } - - func outlineViewItemDidExpand(_ notification: Notification) { - guard let id = workspace?.editorManager.activeEditor.selectedTab?.file.id, - let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true), - /// update outline selection only if the parent of selected item match with expanded item - item.parent === notification.userInfo?["NSObject"] as? CEWorkspaceFile else { - return - } - /// select active file under collapsed folder only if its parent is expanding - if outlineView.isItemExpanded(item.parent) { - updateSelection(itemID: item.id) - } - } - - func outlineViewItemDidCollapse(_ notification: Notification) {} - - func outlineView(_ outlineView: NSOutlineView, itemForPersistentObject object: Any) -> Any? { - guard let id = object as? CEWorkspaceFile.ID, - let item = workspace?.workspaceFileManager?.getFile(id, createIfNotFound: true) else { return nil } - return item - } - - func outlineView(_ outlineView: NSOutlineView, persistentObjectForItem item: Any?) -> Any? { - guard let item = item as? CEWorkspaceFile else { return nil } - return item.id - } - - /// Finds and selects an ``Item`` from an array of ``Item`` and their `children` based on the `id`. - /// - Parameters: - /// - id: the id of the item item - /// - collection: the array to search for - /// - forcesReveal: The boolean to indicates whether or not it should force to reveal the selected file. - private func select(by id: EditorTabID, forcesReveal: Bool) { - guard case .codeEditor(let path) = id, - let item = workspace?.workspaceFileManager?.getFile(path, createIfNotFound: true) else { - return - } - // If the user has set "Reveal file on selection change" to on or it is forced to reveal, - // we need to reveal the item before selecting the row. - if Settings.shared.preferences.general.revealFileOnFocusChange || forcesReveal { - reveal(item) - } - let row = outlineView.row(forItem: item) - if row == -1 { - outlineView.deselectRow(outlineView.selectedRow) - } - shouldSendSelectionUpdate = false - outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false) - shouldSendSelectionUpdate = true - } - - /// Reveals the given `fileItem` in the outline view by expanding all the parent directories of the file. - /// If the file is not found, it will present an alert saying so. - /// - Parameter fileItem: The file to reveal. - public func reveal(_ fileItem: CEWorkspaceFile) { - if let parent = fileItem.parent { - expandParent(item: parent) - } - let row = outlineView.row(forItem: fileItem) - shouldSendSelectionUpdate = false - outlineView.selectRowIndexes(.init(integer: row), byExtendingSelection: false) - shouldSendSelectionUpdate = true - - if row < 0 { - let alert = NSAlert() - alert.messageText = NSLocalizedString( - "Could not find file", - comment: "Could not find file" - ) - alert.runModal() - return - } else { - let visibleRect = scrollView.contentView.visibleRect - let visibleRows = outlineView.rows(in: visibleRect) - guard !visibleRows.contains(row) else { - /// in case that the selected file is not fully visible (some parts are out of the visible rect), - /// `scrollRowToVisible(_:)` method brings the file where it can be fully visible. - outlineView.scrollRowToVisible(row) - return - } - let rowRect = outlineView.rect(ofRow: row) - let centerY = rowRect.midY - (visibleRect.height / 2) - let center = NSPoint(x: 0, y: centerY) - /// `scroll(_:)` method alone doesn't bring the selected file to the center in some cases. - /// calling `scrollRowToVisible(_:)` method before it makes the file reveal in the center more correctly. - outlineView.scrollRowToVisible(row) - outlineView.scroll(center) - } - } - - /// Method for recursively expanding a file's parent directories. - /// - Parameter item: - private func expandParent(item: CEWorkspaceFile) { - if let parent = item.parent as CEWorkspaceFile? { - expandParent(item: parent) - } - outlineView.expandItem(item) - } -} From d27a98e50a16fd02889d5b8742f57bbc8be32073 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:17:05 -0500 Subject: [PATCH 4/6] Finalize Script --- .github/workflows/pre-release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 14d91d9e41..81aae39367 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -136,7 +136,8 @@ jobs: mkdir "$SPARKLE_ARCHIVE" cp "$RUNNER_TEMP/CodeEdit.dmg" "$SPARKLE_ARCHIVE" SPARKLE_SIG=$("$SPARKLE_BIN/sign_update" --ed-key-file "$RUNNER_TEMP/sparkle_key" "$SPARKLE_ARCHIVE/CodeEdit.dmg" | cut -d\" -f2) - "$SPARKLE_BIN/generate_appcast" --ed-key-file "$RUNNER_TEMP/sparkle_key" --download-url-prefix "${{ env.SPARKLE_DL_PREFIX }}/v${{ env.APP_VERSION }}/" --link "$SPARKLE_LINK" --channel "$SPARKLE_CHANNEL" --maximum-deltas 0 "$SPARKLE_ARCHIVE" --release-notes-url-prefix "$RELEASE_NOTES_PREFIX" --full-release-notes-url "$RELEASE_NOTES_URL" + echo "<\!DOCTYPE>" > "$SPARKLE_ARCHIVE/CodeEdit.html" # Need a blank html doc with the DOCTYPE tag to trick sparkle into loading our remote release notes. + "$SPARKLE_BIN/generate_appcast" --ed-key-file "$RUNNER_TEMP/sparkle_key" --download-url-prefix "${{ env.SPARKLE_DL_PREFIX }}/v${{ env.APP_VERSION }}/" --link "$SPARKLE_LINK" --channel "$SPARKLE_CHANNEL" --maximum-deltas 0 "$SPARKLE_ARCHIVE" --release-notes-url-prefix "${{ env.RELEASE_NOTES_PREFIX }}v${{ env.APP_VERSION }}/" --full-release-notes-url "$RELEASE_NOTES_URL" ############################ # Publish Pre Release From e0afd7c6f9ad9002b002999c6f3ae14e6e7338d4 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:22:08 -0500 Subject: [PATCH 5/6] Use new url --- .github/workflows/pre-release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/pre-release.yml b/.github/workflows/pre-release.yml index 81aae39367..e5642f6551 100644 --- a/.github/workflows/pre-release.yml +++ b/.github/workflows/pre-release.yml @@ -128,7 +128,7 @@ jobs: APP_VERSION: ${{ env.APP_VERSION }} APP_BUILD: ${{ env.APP_BUILD }} RELEASE_NOTES_URL: "https://codeedit.app/whats-new/" - RELEASE_NOTES_PREFIX: "https://codeedit.app/whats-new/raw/" + RELEASE_NOTES_PREFIX: "https://codeedit.app/sparkle/" run: | SPARKLE_BIN="$RUNNER_TEMP/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin" SPARKLE_ARCHIVE="$RUNNER_TEMP/Sparkle_Archive" From df1ca3f920ec00079bd691c7095eb4db92b7e1d3 Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:24:36 -0500 Subject: [PATCH 6/6] Enable JS In Release Notes Alert --- CodeEdit/Info.plist | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CodeEdit/Info.plist b/CodeEdit/Info.plist index 16abde1332..3b6cd97ad6 100644 --- a/CodeEdit/Info.plist +++ b/CodeEdit/Info.plist @@ -1286,5 +1286,7 @@ https://github.com/CodeEditApp/CodeEdit/releases/latest/download/appcast.xml SUPublicEDKey /vAnxnK9wj4IqnUt6wS9EN3Ug69zHb+S/Pb9CyZuwa0= + SUEnableJavaScript +