Repository navigation
Add workspace switching by dragging folder onto sidebar #1805
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
wonbywondev
wants to merge
4
commits into
manaflow-ai:main
from
wonbywondev:feat-sidebar-folder-drop
Closed
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
42f334b
Add workspace switching by dragging folder onto sidebar
wonbywondev 9179833
fix: sidebar folder drop — handle browser-only workspace and clarify …
wonbywondev f036221
fix: sidebar folder drop onto browser-only workspace opens terminal s…
wonbywondev 474f7bf
fix: restore first-click focus after browser-panel terminal split
wonbywondev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1133,6 +1133,71 @@ class TabManager: ObservableObject { | |
| return newWorkspace | ||
| } | ||
|
|
||
| /// Handles a folder drag-and-drop onto the sidebar or non-terminal window area. | ||
| /// | ||
| /// - If an existing workspace has the same working directory, selects it and adds a new | ||
| /// terminal split. If the workspace contains only non-terminal panels (e.g. browser-only), | ||
| /// the terminal split is anchored beside the existing panel rather than creating a new workspace. | ||
| /// - Otherwise, creates a new workspace rooted at the dropped folder. | ||
| /// | ||
| /// Only the first plain-directory URL is acted upon. Non-directory URLs and filesystem | ||
| /// packages (.app, .xcworkspace, .playground, etc.) are silently ignored. | ||
| @discardableResult | ||
| func handleSidebarFolderDrop(_ urls: [URL]) -> Bool { | ||
| let folderURLs = urls.filter { url in | ||
| // Accept only plain directories; exclude filesystem packages (.app, .xcworkspace, | ||
| // .playground, etc.) which report isDirectory=true but should be ignored like files. | ||
| guard let values = try? url.resourceValues(forKeys: [.isDirectoryKey, .isPackageKey]) | ||
| else { return false } | ||
| return values.isDirectory == true && values.isPackage != true | ||
| } | ||
| let directories = FinderServicePathResolver.orderedUniqueDirectories( | ||
| from: folderURLs.filter { $0.isFileURL } | ||
| ) | ||
| guard let path = directories.first else { return false } | ||
|
|
||
| #if DEBUG | ||
| dlog("sidebar.folderDrop path=\(path) tabCount=\(tabs.count)") | ||
| #endif | ||
|
|
||
| // Note: resolvingSymlinksInPath is intentionally not applied here. | ||
| // orderedUniqueDirectories already uses standardizedFileURL which resolves /tmp → /private/tmp | ||
| // on macOS. Adding resolvingSymlinksInPath to both sides would be equivalent but adds | ||
| // unnecessary cost; /tmp symlink matching already passes without it. | ||
| let existing = tabs.first { workspace in | ||
| var cd = workspace.currentDirectory.trimmingCharacters(in: .whitespacesAndNewlines) | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| while cd.count > 1 && cd.hasSuffix("/") { cd.removeLast() } | ||
| return cd == path | ||
| } | ||
|
|
||
| if let existing { | ||
| selectedTabId = existing.id | ||
| // Prefer a focused or existing TerminalPanel as the split source. | ||
| // If the workspace has only non-terminal panels (e.g. browser-only), use any panel | ||
| // so a new terminal appears as a split beside it rather than creating a new workspace. | ||
| let panelId = existing.focusedPanelId | ||
| ?? existing.panels.values.compactMap { $0 as? TerminalPanel }.first?.id | ||
| ?? existing.panels.values.first?.id | ||
| if let panelId { | ||
|
cubic-dev-ai[bot] marked this conversation as resolved.
|
||
| let newPanel = existing.newTerminalSplit(from: panelId, orientation: .horizontal) | ||
| #if DEBUG | ||
| if newPanel == nil { | ||
| dlog("sidebar.folderDrop.splitFailed panelId=\(panelId.uuidString.prefix(5))") | ||
| } | ||
| #endif | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| #if DEBUG | ||
| dlog("sidebar.folderDrop.existing workspaceId=\(existing.id.uuidString.prefix(5))") | ||
| #endif | ||
| } else { | ||
| addWorkspace(workingDirectory: path, select: true) | ||
| #if DEBUG | ||
| dlog("sidebar.folderDrop.new path=\(path)") | ||
| #endif | ||
| } | ||
| return true | ||
| } | ||
|
Comment on lines
+1181
to
+1199
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not report success when no terminal was created. At Line 1181 and Line 1198, the method can return Suggested hardening if let existing {
selectedTabId = existing.id
// Prefer a focused or existing TerminalPanel as the split source.
// If the workspace has only non-terminal panels (e.g. browser-only), use any panel
// so a new terminal appears as a split beside it rather than creating a new workspace.
let panelId = existing.focusedPanelId
?? existing.panels.values.compactMap { $0 as? TerminalPanel }.first?.id
?? existing.panels.values.first?.id
- if let panelId {
- let newPanel = existing.newTerminalSplit(from: panelId, orientation: .horizontal)
+ if let panelId,
+ existing.newTerminalSplit(from: panelId, orientation: .horizontal) != nil {
`#if` DEBUG
- if newPanel == nil {
- dlog("sidebar.folderDrop.splitFailed panelId=\(panelId.uuidString.prefix(5))")
- }
+ dlog("sidebar.folderDrop.existing workspaceId=\(existing.id.uuidString.prefix(5))")
`#endif`
+ } else {
+ // Ensure drop still opens a terminal if split source/creation fails.
+ _ = addWorkspace(workingDirectory: path, select: true)
+#if DEBUG
+ dlog("sidebar.folderDrop.fallback.new path=\(path)")
+#endif
}
-#if DEBUG
- dlog("sidebar.folderDrop.existing workspaceId=\(existing.id.uuidString.prefix(5))")
-#endif
} else {
addWorkspace(workingDirectory: path, select: true)
`#if` DEBUG
dlog("sidebar.folderDrop.new path=\(path)")
`#endif`
}
return true🤖 Prompt for AI Agents |
||
|
|
||
| @MainActor | ||
| private func sendWelcomeWhenReady(to workspace: Workspace) { | ||
| if let terminalPanel = workspace.focusedTerminalPanel, | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Limit the folder-drop fallback to the actual sidebar.
These branches now treat any non-terminal, non-browser area as a sidebar drop target, so a plain folder dropped on the titlebar strip or other non-terminal gaps will still show
.copyand create/select a workspace. That broadens the feature beyond the sidebar-only behavior described for this PR.Also applies to: 710-724
🤖 Prompt for AI Agents
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the suggestion, but this guard isn't necessary given how the overlay is structured.
FileDropOverlayViewis anchored tocontentView.topAnchor, which means the title-bar strip above it is physically unreachable by drag-and-drop. Within the overlay's bounds, the only area that is neither a terminal panel nor a browser panel is the sidebar itself — every other pixel is covered by one of those two panel types.Added a comment in
performDragOperationexplaining this so the intent is clear to future readers.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@wonbywondevGot it—thanks for the clarification. I double-checked the overlay install and the new comment in performDragOperation; since the FileDropOverlayView is anchored to contentView (not the native titlebar) and terminal/browser panels occupy the rest of the content area, the fallback effectively maps to “sidebar only.” No further changes needed.✏️ Learnings added
🧠 Learnings used