Skip to content

Add Open Folder in VS Code (Inline) menu item and command palette entry - #2409

Merged
austinywang merged 2 commits into
mainfrom
issue-2401-vscode-web-local-folders
Mar 31, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-2401-vscode-web-local-folders

Conversation

@austinywang

@austinywang austinywang commented Mar 31, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds File > Open Folder in VS Code (Inline)… menu item with an NSOpenPanel folder picker that opens the selected directory in an inline VS Code browser panel
  • Adds a matching command palette entry (Open Folder in VS Code (Inline)…) so the action is discoverable via Cmd-P
  • Extracts the inline VS Code open logic from ContentView into a reusable AppDelegate.openDirectoryInInlineVSCode(_:tabManager:) method shared by the context-menu action, new menu item, and command palette
  • Both menu item and palette entry are hidden/disabled when VS Code is not installed

Closes #2401

Test plan

  • Verify "Open Folder in VS Code (Inline)…" appears in File menu when VS Code is installed
  • Verify the menu item is disabled when VS Code is not installed
  • Verify the command palette entry appears and works
  • Verify the existing right-click "Open in VS Code (Inline)" context action still works
  • Verify the open panel defaults to the focused workspace's cwd

🤖 Generated with Claude Code


Summary by cubic

Adds a File menu item and command palette command to open a folder in inline VS Code, making the action easier to find. Centralizes the open logic and disables the UI when VS Code isn’t installed.

  • New Features

    • File > Open Folder in VS Code (Inline)… opens a folder via a system picker and defaults to the focused workspace’s cwd.
    • Command palette entry “Open Folder in VS Code (Inline)…” with “VS Code Inline” subtitle; only shown/enabled when available.
  • Refactors

    • Moved inline VS Code open logic to AppDelegate.openDirectoryInInlineVSCode(_:tabManager:) and showOpenFolderInInlineVSCodePanel(), shared by the context menu, menu item, and command palette.
    • Added localized strings for menu and command palette (en, ja).

Written for commit a1352e0. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Added "Open Folder in VS Code (Inline)…" to the File menu and command palette, letting you pick a directory via a folder selection panel and open it in the Inline VS Code target.
  • Localization
    • Added English and Japanese localized strings for the new menu/command text and panel prompts.

Adds a File menu item and command palette entry to pick a local folder
and open it in an inline VS Code browser panel. Extracts the inline
VS Code open logic from ContentView into a reusable AppDelegate method
so both the right-click context action and the new open-panel flow
share the same code path.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Mar 31, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Mar 31, 2026 7:16am

@coderabbitai

coderabbitai Bot commented Mar 31, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds an "Open Folder in VS Code (Inline)" flow: localization entries, AppDelegate methods to run the inline VS Code serve-web and open a folder, a directory picker panel, a command-palette command, and a File menu item wired to that flow.

Changes

Cohort / File(s) Summary
Localization
Resources/Localizable.xcstrings
Added 5 new localized keys for the "Open Folder in VS Code (Inline)" command, menu labels, and panel text with English and Japanese values and manual extraction state.
AppDelegate implementation
Sources/AppDelegate.swift
Added openDirectoryInInlineVSCode(_:tabManager:) to resolve inline VS Code target, ensure serve-web URL, build/open the "open folder" URL in the inline browser; added showOpenFolderInInlineVSCodePanel(tabManager:) to present an NSOpenPanel and invoke the opener.
UI / Command wiring
Sources/ContentView.swift, Sources/cmuxApp.swift
Registered palette.openFolderInVSCodeInline command palette entry and handler (delegates to AppDelegate), refactored existing focused-directory opener to call AppDelegate, and added a File menu item that triggers the folder-picker; command/menu enabled only if TerminalDirectoryOpenTarget.vscodeInline.isAvailable().

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as ContentView / cmuxApp
    participant Panel as NSOpenPanel
    participant AD as AppDelegate
    participant Controller as VSCodeServeWebController
    participant Browser as Browser

    User->>UI: Select "Open Folder in VS Code (Inline)"
    UI->>Panel: Show directory selection
    Panel->>UI: Return directory URL (OK)
    UI->>AD: openDirectoryInInlineVSCode(directoryURL)
    AD->>Controller: ensureServeWebURL()
    Controller-->>AD: serve-web URL
    AD->>AD: build "open folder" URL
    AD->>Browser: open URL (prefer split-right)
    Browser->>Browser: display VS Code inline with folder
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

Poem

🐰 I hopped to a panel, bright and kind,
Chose a folder, left no file behind,
Served the web, split right with glee,
VS Code inline — come edit with me! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: adding a File menu item and command palette entry for opening folders in VS Code Inline.
Description check ✅ Passed The PR description includes a clear summary of changes, context from the linked issue, and a test plan; it follows most template guidelines.
Linked Issues check ✅ Passed The PR successfully implements the requirements from issue #2401 by enabling local folder opening via VS Code web through File menu item, command palette entry, and supporting UI disable when VS Code is unavailable.
Out of Scope Changes check ✅ Passed All changes are directly related to adding the File menu item and command palette entry for VS Code Inline folder opening, with no extraneous modifications detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-2401-vscode-web-local-folders

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

@greptile-apps

greptile-apps Bot commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a File > Open Folder in VS Code (Inline)… menu item and a matching command-palette entry, wiring them through a new AppDelegate.openDirectoryInInlineVSCode(_:tabManager:) method that centralises the previously inline logic from ContentView. Both entry points are gated on TerminalDirectoryOpenTarget.vscodeInline.isAvailable() and the panel defaults to the focused workspace's currentDirectory.

Key changes:

  • AppDelegate gains openDirectoryInInlineVSCode (shared open) and showOpenFolderInInlineVSCodePanel (NSOpenPanel wrapper), both reachable from the menu, the palette, and the existing right-click context action.
  • ContentView registers palette.openFolderInVSCodeInline with a when guard and a DispatchQueue.main.async deferral (matching the palette.openFolder pattern) so the panel appears after the command palette has dismissed.
  • cmuxApp adds the Button to the File menu, consistently .disabled when VS Code is absent.
  • Localizable.xcstrings ships English and Japanese strings for all new keys.

Two points worth verifying:

  1. The workspace-ID resolution in openDirectoryInInlineVSCode falls back to addWorkspace(select: true) when both selectedWorkspace and tabs.first are nil — a silent workspace-creation side-effect that may be surprising.
  2. The refactored path now calls openBrowser(inWorkspace:preferSplitRight:true), which reuses an existing top-right pane when one exists, rather than always creating a new split from the focused panel as the old code did — a subtle UX change for the context-menu action.

Confidence Score: 5/5

Safe to merge; all findings are P2 design/edge-case observations with no runtime-breaking defects.

The implementation is consistent with existing patterns (NSOpenPanel modal deferral, isAvailable guards, localization). The two P2 findings are a silent addWorkspace side-effect on a very unlikely empty-tabs edge case, and a minor reuse-pane UX change inherited from openBrowser's heuristic — neither causes data loss, crashes, or broken primary flows.

Sources/AppDelegate.swift — the addWorkspace fallback in openDirectoryInInlineVSCode and the split-reuse behaviour change are worth a second look.

Important Files Changed

Filename Overview
Sources/AppDelegate.swift Adds openDirectoryInInlineVSCode (shared open logic) and showOpenFolderInInlineVSCodePanel (NSOpenPanel wrapper); the workspace-ID fallback chain has a side-effectful addWorkspace call and the refactor subtly changes split behaviour for the context-menu action.
Sources/ContentView.swift Registers the new command-palette entry with proper when guard and DispatchQueue.main.async deferral; replaces the inline openFocusedDirectoryInInlineVSCode body with a one-line delegation to AppDelegate.
Sources/cmuxApp.swift Adds the "Open Folder in VS Code (Inline)…" Button to the File menu, correctly .disabled when VS Code is unavailable.
Resources/Localizable.xcstrings Adds English and Japanese localizations for the two new command-palette keys and the three new menu-item keys; all entries look complete and correctly structured.

Sequence Diagram

sequenceDiagram
    actor User
    participant Menu as File Menu / CmdPalette
    participant AppDelegate
    participant NSOpenPanel
    participant VSCodeController as VSCodeServeWebController
    participant TabManager

    User->>Menu: Click "Open Folder in VS Code (Inline)…"
    Menu->>AppDelegate: showOpenFolderInInlineVSCodePanel(tabManager?)
    AppDelegate->>AppDelegate: guard vscodeInline.isAvailable()
    AppDelegate->>AppDelegate: resolve targetTabManager
    AppDelegate->>NSOpenPanel: runModal() — seeded with workspace cwd
    NSOpenPanel-->>AppDelegate: .OK + url
    AppDelegate->>AppDelegate: openDirectoryInInlineVSCode(url, tabManager)
    AppDelegate->>AppDelegate: guard vscodeInline.applicationURL()
    AppDelegate->>AppDelegate: resolve targetWorkspaceId
    AppDelegate->>VSCodeController: ensureServeWebURL(vscodeApplicationURL)
    VSCodeController-->>AppDelegate: serveWebURL (async callback)
    AppDelegate->>AppDelegate: build openFolderURL
    AppDelegate->>TabManager: openBrowser(inWorkspace: targetWorkspaceId, url: openFolderURL, preferSplitRight: true)
    TabManager-->>AppDelegate: panelId (or nil → NSSound.beep)
Loading

Reviews (1): Last reviewed commit: "Add "Open Folder in VS Code (Inline)" vi..." | Re-trigger Greptile

Comment thread Sources/AppDelegate.swift
Comment on lines +5679 to +5681
let targetWorkspaceId = targetTabManager.selectedWorkspace?.id
?? targetTabManager.tabs.first?.id
?? targetTabManager.addWorkspace(select: true).id

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Side-effectful fallback creates a workspace silently

The third branch of the ?? chain eagerly calls addWorkspace(select: true) as a side-effect of what appears to be a simple ID lookup. If selectedWorkspace is nil AND tabs.first is nil (e.g., the user has closed all tabs in the window between the panel's appear and the OK click), a brand-new, empty workspace is created just to serve as the target for the VS Code browser — which can be confusing and unexpected.

Consider guarding against this case instead:

guard let targetWorkspaceId = targetTabManager.selectedWorkspace?.id
    ?? targetTabManager.tabs.first?.id else {
    return false
}

If there are genuinely no workspaces, returning false (and letting showOpenFolderInInlineVSCodePanel emit a beep) is safer than silently creating one.

Comment thread Sources/AppDelegate.swift
Comment on lines +5664 to +5704
@discardableResult
func openDirectoryInInlineVSCode(
_ directoryURL: URL,
tabManager preferredTabManager: TabManager? = nil
) -> Bool {
guard let vscodeApplicationURL = TerminalDirectoryOpenTarget.vscodeInline.applicationURL() else {
return false
}

let targetTabManager = preferredTabManager
?? preferredMainWindowContextForWorkspaceCreation(debugSource: "inlineVSCode.open.target")?.tabManager
guard let targetTabManager else {
return false
}

let targetWorkspaceId = targetTabManager.selectedWorkspace?.id
?? targetTabManager.tabs.first?.id
?? targetTabManager.addWorkspace(select: true).id
let normalizedDirectoryURL = directoryURL.standardizedFileURL

VSCodeServeWebController.shared.ensureServeWebURL(vscodeApplicationURL: vscodeApplicationURL) { serveWebURL in
guard let serveWebURL,
let openFolderURL = VSCodeServeWebURLBuilder.openFolderURL(
baseWebUIURL: serveWebURL,
directoryPath: normalizedDirectoryURL.path
) else {
NSSound.beep()
return
}

guard targetTabManager.openBrowser(
inWorkspace: targetWorkspaceId,
url: openFolderURL,
preferSplitRight: true
) != nil else {
NSSound.beep()
return
}
}

return true

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Behavioral change for existing context-menu action

The refactored openDirectoryInInlineVSCode delegates to openBrowser(inWorkspace:url:preferSplitRight:true), which uses topRightBrowserReusePane() to reuse an existing right-pane when the workspace is already split. The previous implementation called newBrowserSplit(from: focusedPanelId, …) unconditionally, always creating a new split directly from the right-clicked terminal.

As a result, when a workspace already has multiple panes, the right-click context-menu "Open in VS Code (Inline)" action will now reuse the top-right pane (loading a new surface there) rather than always appending a new split adjacent to the focused terminal. The test plan checks whether the context action still works, but doesn't explicitly verify the split position. It's worth confirming the new reuse-pane behaviour is intentional for the context-menu case (it may well be — fewer unwanted splits is arguably better UX).

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
Sources/ContentView.swift (1)

5553-5571: Fold VS Code availability into the palette fingerprint.

This when clause bypasses CommandPaletteContextSnapshot, so the cached command corpus will not rebuild if VS Code is installed or removed while the palette is already open. Consider threading this through the snapshot/fingerprint path, like the terminal open-target availability flags, so the visible command list stays in sync.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 5553 - 5571, The command's when
closure calls TerminalDirectoryOpenTarget.vscodeInline.isAvailable() directly,
bypassing CommandPaletteContextSnapshot and so changes in VS Code availability
won't update the cached palette fingerprint; update the palette fingerprint
pathway to include a boolean availability flag for VS Code (similar to the
existing terminal open-target availability flags), add that flag to
CommandPaletteContextSnapshot (or the fingerprint provider), and change the
CommandPaletteCommandContribution's when closure to consult the
snapshot/fingerprint flag instead of calling
TerminalDirectoryOpenTarget.vscodeInline.isAvailable() directly so the corpus
rebuilds when VS Code is installed or removed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/AppDelegate.swift`:
- Around line 5679-5698: targetWorkspaceId is captured before the async
ensureServeWebURL completes, causing stale workspace selection; move the
workspace-id computation into the ensureServeWebURL completion closure
(re-evaluate targetTabManager.selectedWorkspace?.id ??
targetTabManager.tabs.first?.id ?? targetTabManager.addWorkspace(select:
true).id inside the closure) and then call
targetTabManager.openBrowser(inWorkspace:url:preferSplitRight:) with that
freshly computed id so tab/workspace changes during serve startup don't cause
unnecessary failures.

---

Nitpick comments:
In `@Sources/ContentView.swift`:
- Around line 5553-5571: The command's when closure calls
TerminalDirectoryOpenTarget.vscodeInline.isAvailable() directly, bypassing
CommandPaletteContextSnapshot and so changes in VS Code availability won't
update the cached palette fingerprint; update the palette fingerprint pathway to
include a boolean availability flag for VS Code (similar to the existing
terminal open-target availability flags), add that flag to
CommandPaletteContextSnapshot (or the fingerprint provider), and change the
CommandPaletteCommandContribution's when closure to consult the
snapshot/fingerprint flag instead of calling
TerminalDirectoryOpenTarget.vscodeInline.isAvailable() directly so the corpus
rebuilds when VS Code is installed or removed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1b423c9c-976b-4803-b544-ab99cb1eda56

📥 Commits

Reviewing files that changed from the base of the PR and between 341aed8 and 1918264.

📒 Files selected for processing (4)
  • Resources/Localizable.xcstrings
  • Sources/AppDelegate.swift
  • Sources/ContentView.swift
  • Sources/cmuxApp.swift

Comment thread Sources/AppDelegate.swift
Comment on lines +5679 to +5698
let targetWorkspaceId = targetTabManager.selectedWorkspace?.id
?? targetTabManager.tabs.first?.id
?? targetTabManager.addWorkspace(select: true).id
let normalizedDirectoryURL = directoryURL.standardizedFileURL

VSCodeServeWebController.shared.ensureServeWebURL(vscodeApplicationURL: vscodeApplicationURL) { serveWebURL in
guard let serveWebURL,
let openFolderURL = VSCodeServeWebURLBuilder.openFolderURL(
baseWebUIURL: serveWebURL,
directoryPath: normalizedDirectoryURL.path
) else {
NSSound.beep()
return
}

guard targetTabManager.openBrowser(
inWorkspace: targetWorkspaceId,
url: openFolderURL,
preferSplitRight: true
) != nil else {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Recompute workspace ID inside the async callback to avoid stale-target failures.

targetWorkspaceId is captured before ensureServeWebURL completes (Line 5679). If tabs/workspaces change while serve-web starts, openBrowser(inWorkspace:...) can fail unnecessarily.

Suggested fix
-        let targetWorkspaceId = targetTabManager.selectedWorkspace?.id
-            ?? targetTabManager.tabs.first?.id
-            ?? targetTabManager.addWorkspace(select: true).id
         let normalizedDirectoryURL = directoryURL.standardizedFileURL

         VSCodeServeWebController.shared.ensureServeWebURL(vscodeApplicationURL: vscodeApplicationURL) { serveWebURL in
             guard let serveWebURL,
                   let openFolderURL = VSCodeServeWebURLBuilder.openFolderURL(
                       baseWebUIURL: serveWebURL,
                       directoryPath: normalizedDirectoryURL.path
                   ) else {
                 NSSound.beep()
                 return
             }

+            let targetWorkspaceId = targetTabManager.selectedWorkspace?.id
+                ?? targetTabManager.tabs.first?.id
+                ?? targetTabManager.addWorkspace(select: true).id
             guard targetTabManager.openBrowser(
                 inWorkspace: targetWorkspaceId,
                 url: openFolderURL,
                 preferSplitRight: true
             ) != nil else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/AppDelegate.swift` around lines 5679 - 5698, targetWorkspaceId is
captured before the async ensureServeWebURL completes, causing stale workspace
selection; move the workspace-id computation into the ensureServeWebURL
completion closure (re-evaluate targetTabManager.selectedWorkspace?.id ??
targetTabManager.tabs.first?.id ?? targetTabManager.addWorkspace(select:
true).id inside the closure) and then call
targetTabManager.openBrowser(inWorkspace:url:preferSplitRight:) with that
freshly computed id so tab/workspace changes during serve startup don't cause
unnecessary failures.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
Sources/ContentView.swift (1)

5386-5404: Fingerprint inline VS Code availability instead of reading it ad hoc.

This when closure depends on TerminalDirectoryOpenTarget.vscodeInline.isAvailable(), but the command-palette corpus is invalidated from CommandPaletteContextSnapshot. If VS Code availability changes while the palette is open, this entry can stay stale until a forced refresh. Moving that availability bit into the snapshot would keep visibility aligned with the existing cache key.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` around lines 5386 - 5404, The command palette
entry currently calls TerminalDirectoryOpenTarget.vscodeInline.isAvailable()
directly in the when closure which can become stale; instead add a boolean flag
to CommandPaletteContextSnapshot (e.g. vscodeInlineAvailable or an accessor like
isVscodeInlineAvailable()), populate that flag when building the snapshot where
terminal/availability is resolved, and change the
CommandPaletteCommandContribution's when closure to read from the snapshot (via
the provided context) rather than calling
TerminalDirectoryOpenTarget.vscodeInline.isAvailable() directly so visibility is
driven by the cached snapshot key.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@Sources/ContentView.swift`:
- Around line 5386-5404: The command palette entry currently calls
TerminalDirectoryOpenTarget.vscodeInline.isAvailable() directly in the when
closure which can become stale; instead add a boolean flag to
CommandPaletteContextSnapshot (e.g. vscodeInlineAvailable or an accessor like
isVscodeInlineAvailable()), populate that flag when building the snapshot where
terminal/availability is resolved, and change the
CommandPaletteCommandContribution's when closure to read from the snapshot (via
the provided context) rather than calling
TerminalDirectoryOpenTarget.vscodeInline.isAvailable() directly so visibility is
driven by the cached snapshot key.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: bbff7403-20e2-4d93-bcb7-ca67e76c0958

📥 Commits

Reviewing files that changed from the base of the PR and between 1918264 and a1352e0.

📒 Files selected for processing (1)
  • Sources/ContentView.swift

@austinywang
austinywang merged commit 922c454 into main Mar 31, 2026
15 checks passed
@austinywang austinywang mentioned this pull request Apr 6, 2026
3 tasks

This branch was successfully deployed

1 active deployment
Preview — a1352e0b Deployed Mar 31, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support opening local folders to use VS Code web version

1 participant