Skip to content

Commit e600213

Browse files
fix(open vscode): launch via in-bundle code CLI on macOS
`open -a <App> --args` only forwards args on a cold launch; when VS Code is already running macOS drops the args and the vscode:// URL never reaches the mssql extension, so `sqlcmd open vscode` silently no-ops. Add a platform-specific VSCode.launch that on darwin execs <App>/Contents/Resources/app/bin/code --open-url <url> directly, bypassing /usr/bin/open. Linux and Windows continue to use tool.Run unchanged. Extract tool.runCmd so platform launchers can supply their own *exec.Cmd while keeping the early-exit / stdio handling.
1 parent 432486c commit e600213

5 files changed

Lines changed: 44 additions & 6 deletions

File tree

internal/tools/tool/tool.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,8 +73,15 @@ func (t *tool) Run(args []string) (int, error) {
7373
return 1, fmt.Errorf("internal error: Call IsInstalled before Run")
7474
}
7575

76-
cmd := t.generateCommandLine(args)
76+
return t.runCmd(t.generateCommandLine(args))
77+
}
7778

79+
// runCmd starts cmd, waits briefly for an early failure, and otherwise treats
80+
// the launch as successful and lets the GUI tool keep running. Callers that
81+
// need to bypass generateCommandLine (e.g. VS Code on darwin, which must exec
82+
// the in-bundle `code` CLI directly rather than `open -a`) build their own
83+
// *exec.Cmd and call this helper.
84+
func (t *tool) runCmd(cmd *exec.Cmd) (int, error) {
7885
// Normalize argv[0] so it matches cmd.Path on every platform. The darwin
7986
// implementation builds Args starting at "-a" because it shells out via
8087
// /usr/bin/open; without this fix the launched process sees "-a" as argv[0]

internal/tools/tool/vscode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ func (t *VSCode) buildsToSearch() []string {
6060

6161
func (t *VSCode) Run(args []string) (int, error) {
6262
if !test.IsRunningInTestExecutor() {
63-
return t.tool.Run(args)
63+
return t.launch(args)
6464
}
6565
return 0, nil
6666
}

internal/tools/tool/vscode_darwin.go

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
package tool
55

66
import (
7+
"fmt"
78
"os"
9+
"os/exec"
810
"path/filepath"
911
)
1012

11-
// searchLocations returns the .app bundle paths to probe. tool_darwin.go's
12-
// generateCommandLine launches via "open -a <path> --args ...", which expects
13-
// either a registered app name or a .app bundle path; pointing it at the
14-
// in-bundle code binary would fail with "Unable to find application".
13+
// searchLocations returns the .app bundle paths to probe. We resolve at the
14+
// bundle level (not the in-bundle `code` CLI) so IsInstalled keeps working
15+
// when the bundle exists but the optional CLI shim is missing, and so
16+
// tests/users can point --build at the bundle they care about. launch()
17+
// derives the in-bundle CLI from the bundle path at execution time.
1518
func (t *VSCode) searchLocations() []string {
1619
userProfile := os.Getenv("HOME")
1720

@@ -35,6 +38,20 @@ func (t *VSCode) searchLocations() []string {
3538
return locations
3639
}
3740

41+
// launch executes VS Code with the given args. macOS's `open -a <App> --args`
42+
// only forwards args on a cold launch; when VS Code is already running the
43+
// args (including the vscode:// URL we use to drive the mssql extension) are
44+
// silently dropped. Bypass `open` entirely and exec the in-bundle `code` CLI,
45+
// which handles --open-url whether or not VS Code is already running.
46+
func (t *VSCode) launch(args []string) (int, error) {
47+
cliPath := filepath.Join(t.exeName, "Contents", "Resources", "app", "bin", "code")
48+
if _, err := os.Stat(cliPath); err != nil {
49+
return 1, fmt.Errorf("VS Code CLI not found at %q: %w", cliPath, err)
50+
}
51+
cmd := exec.Command(cliPath, args...)
52+
return t.runCmd(cmd)
53+
}
54+
3855
func (t *VSCode) installText() string {
3956
return `Install using Homebrew:
4057

internal/tools/tool/vscode_linux.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ func (t *VSCode) searchLocations() []string {
3737
return locations
3838
}
3939

40+
// launch executes VS Code with the given args. On Linux exeName is already the
41+
// `code` CLI, which handles --open-url correctly whether or not VS Code is
42+
// already running, so we go through the standard tool.Run path.
43+
func (t *VSCode) launch(args []string) (int, error) {
44+
return t.tool.Run(args)
45+
}
46+
4047
func (t *VSCode) installText() string {
4148
return `Install using a package manager:
4249

internal/tools/tool/vscode_windows.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,13 @@ func vscodeRegistryInstallLocation(build string) string {
131131
return ""
132132
}
133133

134+
// launch executes VS Code with the given args. On Windows exeName is Code.exe,
135+
// which handles --open-url correctly whether or not VS Code is already
136+
// running, so we go through the standard tool.Run path.
137+
func (t *VSCode) launch(args []string) (int, error) {
138+
return t.tool.Run(args)
139+
}
140+
134141
func (t *VSCode) installText() string {
135142
return `Install using a package manager:
136143

0 commit comments

Comments
 (0)