Skip to content

Commit 59cb805

Browse files
committed
feat(VSCode): add support for IDE inside Flatpak
1 parent 0c6b3c3 commit 59cb805

File tree

5 files changed

+116
-65
lines changed

5 files changed

+116
-65
lines changed

cmd/up.go

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,7 @@ func (cmd *UpCmd) Run(
197197
}
198198

199199
// configure container ssh
200+
var sshConfigPath string
200201
if cmd.ConfigureSSH {
201202
devPodHome := ""
202203
envDevPodHome, ok := os.LookupEnv("DEVPOD_HOME")
@@ -205,7 +206,7 @@ func (cmd *UpCmd) Run(
205206
}
206207
setupGPGAgentForwarding := cmd.GPGAgentForwarding || devPodConfig.ContextOption(config.ContextOptionGPGAgentForwarding) == "true"
207208

208-
err = configureSSH(devPodConfig, client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome)
209+
sshConfigPath, err = configureSSH(devPodConfig, client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome)
209210
if err != nil {
210211
return err
211212
}
@@ -238,6 +239,7 @@ func (cmd *UpCmd) Run(
238239
result.SubstitutionContext.ContainerWorkspaceFolder,
239240
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
240241
vscode.FlavorStable,
242+
sshConfigPath,
241243
log,
242244
)
243245
case string(config.IDEVSCodeInsiders):
@@ -247,6 +249,7 @@ func (cmd *UpCmd) Run(
247249
result.SubstitutionContext.ContainerWorkspaceFolder,
248250
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
249251
vscode.FlavorInsiders,
252+
sshConfigPath,
250253
log,
251254
)
252255
case string(config.IDECursor):
@@ -256,6 +259,7 @@ func (cmd *UpCmd) Run(
256259
result.SubstitutionContext.ContainerWorkspaceFolder,
257260
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
258261
vscode.FlavorCursor,
262+
sshConfigPath,
259263
log,
260264
)
261265
case string(config.IDECodium):
@@ -265,6 +269,17 @@ func (cmd *UpCmd) Run(
265269
result.SubstitutionContext.ContainerWorkspaceFolder,
266270
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
267271
vscode.FlavorCodium,
272+
sshConfigPath,
273+
log,
274+
)
275+
case string(config.IDECodiumInsiders):
276+
return vscode.Open(
277+
ctx,
278+
client.Workspace(),
279+
result.SubstitutionContext.ContainerWorkspaceFolder,
280+
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
281+
vscode.FlavorCodiumInsiders,
282+
sshConfigPath,
268283
log,
269284
)
270285
case string(config.IDEPositron):
@@ -274,6 +289,7 @@ func (cmd *UpCmd) Run(
274289
result.SubstitutionContext.ContainerWorkspaceFolder,
275290
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
276291
vscode.FlavorPositron,
292+
sshConfigPath,
277293
log,
278294
)
279295
case string(config.IDEOpenVSCode):
@@ -995,10 +1011,10 @@ func startBrowserTunnel(
9951011
return nil
9961012
}
9971013

998-
func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) error {
1014+
func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) (string, error) {
9991015
path, err := devssh.ResolveSSHConfigPath(sshConfigPath)
10001016
if err != nil {
1001-
return errors.Wrap(err, "Invalid ssh config path")
1017+
return "", errors.Wrap(err, "Invalid ssh config path")
10021018
}
10031019
sshConfigPath = path
10041020

@@ -1013,10 +1029,10 @@ func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfi
10131029
log.Default,
10141030
)
10151031
if err != nil {
1016-
return err
1032+
return "", err
10171033
}
10181034

1019-
return nil
1035+
return sshConfigPath, nil
10201036
}
10211037

10221038
func mergeDevPodUpOptions(baseOptions *provider2.CLIOptions) error {

pkg/config/ide.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,5 +24,6 @@ const (
2424
IDEPositron IDE = "positron"
2525
IDEMarimo IDE = "marimo"
2626
IDECodium IDE = "codium"
27+
IDECodiumInsiders IDE = "codium-insiders"
2728
IDEZed IDE = "zed"
2829
)

pkg/ide/ideparse/parse.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,13 @@ var AllowedIDEs = []AllowedIDE{
164164
Icon: "https://devpod.sh/assets/codium.svg",
165165
Experimental: true,
166166
},
167+
{
168+
Name: config.IDECodiumInsiders,
169+
DisplayName: "Codium Insiders",
170+
Options: vscode.Options,
171+
Icon: "https://devpod.sh/assets/codium_insiders.svg", // TODO to be uploaded
172+
Experimental: true,
173+
},
167174
{
168175
Name: config.IDEPositron,
169176
DisplayName: "Positron",

pkg/ide/vscode/open.go

Lines changed: 81 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,27 @@ import (
66
"fmt"
77
"os/exec"
88
"runtime"
9+
"slices"
910
"strings"
1011

1112
"github.com/loft-sh/devpod/pkg/command"
1213
"github.com/loft-sh/log"
1314
"github.com/skratchdot/open-golang/open"
1415
)
1516

16-
func Open(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, log log.Logger) error {
17+
const (
18+
FlatpakStable string = "com.visualstudio.code"
19+
FlatpakInsiders string = "com.visualstudio.code.insiders"
20+
FlatpakCodium string = "com.vscodium.codium"
21+
FlatpakCodiumInsiders string = "com.vscodium.codium-insiders"
22+
)
23+
24+
func Open(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, sshConfigPath string, log log.Logger) error {
1725
log.Infof("Starting %s...", flavor.DisplayName())
18-
cliErr := openViaCLI(ctx, workspace, folder, newWindow, flavor, log)
19-
if cliErr == nil {
26+
cliErr := openViaCLI(ctx, workspace, folder, newWindow, flavor, sshConfigPath, log)
27+
if cliErr != nil {
28+
log.Debugf("Error opening %s via cli: %v", flavor, cliErr)
29+
} else {
2030
return nil
2131
}
2232

@@ -41,6 +51,8 @@ func openViaBrowser(workspace, folder string, newWindow bool, flavor Flavor, log
4151
protocol = `positron://`
4252
case FlavorCodium:
4353
protocol = `codium://`
54+
case FlavorCodiumInsiders:
55+
protocol = `codium-insiders://`
4456
}
4557

4658
openURL := protocol + `vscode-remote/ssh-remote+` + workspace + `.devpod/` + folder
@@ -58,20 +70,44 @@ func openViaBrowser(workspace, folder string, newWindow bool, flavor Flavor, log
5870
return nil
5971
}
6072

61-
func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, log log.Logger) error {
73+
func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, sshConfigPath string, log log.Logger) error {
6274
// try to find code cli
63-
codePath := findCLI(flavor)
64-
if codePath == "" {
75+
codePath := findCLI(flavor, log)
76+
if codePath == nil {
6577
return fmt.Errorf("couldn't find the %s binary", flavor)
6678
}
6779

80+
if codePath[0] == "flatpak" {
81+
log.Debugf("Running with Flatpak suing the package %s.", codePath[2])
82+
out, err := exec.Command(codePath[0], "ps", "--columns=application").Output()
83+
if err != nil {
84+
return command.WrapCommandError(out, err)
85+
}
86+
splitted := strings.Split(string(out), "\n")
87+
foundRunning := false
88+
// Ignore the header
89+
for _, str := range splitted[1:] {
90+
if strings.TrimSpace(str) == codePath[2] {
91+
foundRunning = true
92+
break
93+
}
94+
}
95+
96+
if foundRunning {
97+
log.Warnf("The IDE is already running via Flatpak. If you are encountering SSH connectivity issues, make sure to give read access to your SSH config file (e.g flatpak override %s --filesystem=%s) and restart your IDE.", codePath[2], sshConfigPath)
98+
}
99+
100+
codePath = slices.Insert(codePath, 2, fmt.Sprintf("--filesystem=%s:ro", sshConfigPath))
101+
}
102+
68103
sshExtension := "ms-vscode-remote.remote-ssh"
69-
if flavor == FlavorCodium {
104+
if flavor == FlavorCodium || flavor == FlavorCodiumInsiders {
70105
sshExtension = "jeanp413.open-remote-ssh"
71106
}
72107

73108
// make sure ms-vscode-remote.remote-ssh is installed
74-
out, err := exec.Command(codePath, "--list-extensions").Output()
109+
listArgs := append(codePath, "--list-extensions")
110+
out, err := exec.Command(listArgs[0], listArgs[1:]...).Output()
75111
if err != nil {
76112
return command.WrapCommandError(out, err)
77113
}
@@ -88,9 +124,9 @@ func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, f
88124

89125
// install remote-ssh extension
90126
if !found {
91-
args := []string{"--install-extension", sshExtension}
92-
log.Debugf("Run vscode command %s %s", codePath, strings.Join(args, " "))
93-
out, err := exec.CommandContext(ctx, codePath, args...).Output()
127+
args := append(codePath, "--install-extension", sshExtension)
128+
log.Debugf("Run vscode command %s %s", args[0], strings.Join(args[1:], " "))
129+
out, err := exec.CommandContext(ctx, args[0], args[1:]...).Output()
94130
if err != nil {
95131
return fmt.Errorf("install ssh extension: %w", command.WrapCommandError(out, err))
96132
}
@@ -108,66 +144,56 @@ func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, f
108144
}
109145
// Needs to be separated by `=` because of windows
110146
folderUriArg := fmt.Sprintf("--folder-uri=vscode-remote://ssh-remote+%s.devpod/%s", workspace, folder)
147+
args = append(codePath, args...)
111148
args = append(args, folderUriArg)
112-
log.Debugf("Run %s command %s %s", flavor.DisplayName(), codePath, strings.Join(args, " "))
113-
out, err = exec.CommandContext(ctx, codePath, args...).CombinedOutput()
149+
log.Debugf("Run %s command %s %s", flavor.DisplayName(), args[0], strings.Join(args[1:], " "))
150+
out, err = exec.CommandContext(ctx, args[0], args[1:]...).CombinedOutput()
114151
if err != nil {
115152
return command.WrapCommandError(out, err)
116153
}
117154

118155
return nil
119156
}
120157

121-
func findCLI(flavor Flavor) string {
122-
if flavor == FlavorStable {
123-
if command.Exists("code") {
124-
return "code"
125-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code") {
126-
return "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
127-
}
128-
129-
return ""
158+
func existsInFlatpak(packageName string, log log.Logger) bool {
159+
if err := exec.Command("flatpak", "info", packageName).Run(); err == nil {
160+
return true
161+
} else {
162+
log.Debugf("Flatpak command for %s returned: %s", packageName, err)
130163
}
164+
return false
165+
}
131166

132-
if flavor == FlavorInsiders {
133-
if command.Exists("code-insiders") {
134-
return "code-insiders"
135-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code") {
136-
return "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"
137-
}
138-
139-
return ""
167+
func getCommandArgs(execName, macOSPath, flatpakPackage string, log log.Logger) []string {
168+
if command.Exists(execName) {
169+
return []string{execName}
140170
}
141171

142-
if flavor == FlavorCursor {
143-
if command.Exists("cursor") {
144-
return "cursor"
145-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Cursor.app/Contents/Resources/app/bin/cursor") {
146-
return "/Applications/Cursor.app/Contents/Resources/app/bin/cursor"
147-
}
148-
149-
return ""
172+
if runtime.GOOS == "darwin" && command.Exists(macOSPath) {
173+
return []string{macOSPath}
150174
}
151175

152-
if flavor == FlavorPositron {
153-
if command.Exists("positron") {
154-
return "positron"
155-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Positron.app/Contents/Resources/app/bin/positron") {
156-
return "/Applications/Positron.app/Contents/Resources/app/bin/positron"
157-
}
158-
159-
return ""
176+
if runtime.GOOS == "linux" && flatpakPackage != "" && command.Exists("flatpak") && existsInFlatpak(flatpakPackage, log) {
177+
return []string{"flatpak", "run", flatpakPackage}
160178
}
161179

162-
if flavor == FlavorCodium {
163-
if command.Exists("codium") {
164-
return "codium"
165-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Codium.app/Contents/Resources/app/bin/codium") {
166-
return "/Applications/Codium.app/Contents/Resources/app/bin/codium"
167-
}
180+
return nil
181+
}
168182

169-
return ""
183+
func findCLI(flavor Flavor, log log.Logger) []string {
184+
switch flavor {
185+
case FlavorStable:
186+
return getCommandArgs("code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", FlatpakStable, log)
187+
case FlavorInsiders:
188+
return getCommandArgs("code-insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", FlatpakInsiders, log)
189+
case FlavorCursor:
190+
return getCommandArgs("cursor", "/Applications/Cursor.app/Contents/Resources/app/bin/cursor", "", log)
191+
case FlavorPositron:
192+
return getCommandArgs("positron", "/Applications/Positron.app/Contents/Resources/app/bin/positron", "", log)
193+
case FlavorCodium:
194+
return getCommandArgs("codium", "/Applications/Codium.app/Contents/Resources/app/bin/codium", FlatpakCodium, log)
195+
case FlavorCodiumInsiders:
196+
return getCommandArgs("codium-insiders", "/Applications/CodiumInsiders.app/Contents/Resources/app/bin/codium-insiders", FlatpakCodiumInsiders, log)
170197
}
171-
172-
return ""
198+
return nil
173199
}

pkg/ide/vscode/vscode.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,12 @@ const (
2626
type Flavor string
2727

2828
const (
29-
FlavorStable Flavor = "stable"
30-
FlavorInsiders Flavor = "insiders"
31-
FlavorCursor Flavor = "cursor"
32-
FlavorPositron Flavor = "positron"
33-
FlavorCodium Flavor = "codium"
29+
FlavorStable Flavor = "stable"
30+
FlavorInsiders Flavor = "insiders"
31+
FlavorCursor Flavor = "cursor"
32+
FlavorPositron Flavor = "positron"
33+
FlavorCodium Flavor = "codium"
34+
FlavorCodiumInsiders Flavor = "codium-insiders"
3435
)
3536

3637
func (f Flavor) DisplayName() string {

0 commit comments

Comments
 (0)