Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/CI-release-notes.yml
Original file line number Diff line number Diff line change
@@ -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 }}
10 changes: 8 additions & 2 deletions .github/workflows/pre-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,27 @@ 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/sparkle/"
run: |
SPARKLE_BIN="$RUNNER_TEMP/DerivedData/SourcePackages/artifacts/sparkle/Sparkle/bin"
SPARKLE_ARCHIVE="$RUNNER_TEMP/Sparkle_Archive"
echo -n "$SPARKLE_KEY" | tee "$RUNNER_TEMP/sparkle_key"
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"
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
Expand Down
8 changes: 8 additions & 0 deletions CodeEdit.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1007,6 +1009,8 @@
6C5C891A2A3F736500A94FE1 /* FocusedValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FocusedValues.swift; sourceTree = "<group>"; };
6C5FDF7929E6160000BC08C0 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = "<group>"; };
6C6362D32C3E321A0025570D /* Editor+History.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Editor+History.swift"; sourceTree = "<group>"; };
6C67413D2C44A28C00AABDF5 /* ProjectNavigatorViewController+DataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+DataSource.swift"; sourceTree = "<group>"; };
6C67413F2C44A2A200AABDF5 /* ProjectNavigatorViewController+Delegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ProjectNavigatorViewController+Delegate.swift"; sourceTree = "<group>"; };
6C6BD6EE29CD12E900235D17 /* ExtensionManagerWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionManagerWindow.swift; sourceTree = "<group>"; };
6C6BD6F029CD13FA00235D17 /* ExtensionDiscovery.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionDiscovery.swift; sourceTree = "<group>"; };
6C6BD6F529CD145F00235D17 /* ExtensionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExtensionInfo.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
@@ -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)
}
}
Loading