Skip to content

Commit cd4654f

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

File tree

5 files changed

+113
-64
lines changed

5 files changed

+113
-64
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: 78 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,24 @@ 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)
26+
cliErr := openViaCLI(ctx, workspace, folder, newWindow, flavor, sshConfigPath, log)
1927
if cliErr == nil {
2028
return nil
2129
}
@@ -41,6 +49,8 @@ func openViaBrowser(workspace, folder string, newWindow bool, flavor Flavor, log
4149
protocol = `positron://`
4250
case FlavorCodium:
4351
protocol = `codium://`
52+
case FlavorCodiumInsiders:
53+
protocol = `codium-insiders://`
4454
}
4555

4656
openURL := protocol + `vscode-remote/ssh-remote+` + workspace + `.devpod/` + folder
@@ -58,20 +68,44 @@ func openViaBrowser(workspace, folder string, newWindow bool, flavor Flavor, log
5868
return nil
5969
}
6070

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

78+
if codePath[0] == "flatpak" {
79+
log.Debugf("Running with Flatpak suing the package %s.", codePath[2])
80+
out, err := exec.Command(codePath[0], "ps", "--columns=application").Output()
81+
if err != nil {
82+
return command.WrapCommandError(out, err)
83+
}
84+
splitted := strings.Split(string(out), "\n")
85+
foundRunning := false
86+
// Ignore the header
87+
for _, str := range splitted[1:] {
88+
if strings.TrimSpace(str) == codePath[2] {
89+
foundRunning = true
90+
break
91+
}
92+
}
93+
94+
if foundRunning {
95+
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)
96+
}
97+
98+
codePath = slices.Insert(codePath, 2, fmt.Sprintf("--filesystem=%s:ro", sshConfigPath))
99+
}
100+
68101
sshExtension := "ms-vscode-remote.remote-ssh"
69-
if flavor == FlavorCodium {
102+
if flavor == FlavorCodium || flavor == FlavorCodiumInsiders {
70103
sshExtension = "jeanp413.open-remote-ssh"
71104
}
72105

73106
// make sure ms-vscode-remote.remote-ssh is installed
74-
out, err := exec.Command(codePath, "--list-extensions").Output()
107+
listArgs := append(codePath, "--list-extensions")
108+
out, err := exec.Command(listArgs[0], listArgs[1:]...).Output()
75109
if err != nil {
76110
return command.WrapCommandError(out, err)
77111
}
@@ -88,9 +122,9 @@ func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, f
88122

89123
// install remote-ssh extension
90124
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()
125+
args := append(codePath, "--install-extension", sshExtension)
126+
log.Debugf("Run vscode command %s %s", args[0], strings.Join(args[1:], " "))
127+
out, err := exec.CommandContext(ctx, args[0], args[1:]...).Output()
94128
if err != nil {
95129
return fmt.Errorf("install ssh extension: %w", command.WrapCommandError(out, err))
96130
}
@@ -108,66 +142,56 @@ func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, f
108142
}
109143
// Needs to be separated by `=` because of windows
110144
folderUriArg := fmt.Sprintf("--folder-uri=vscode-remote://ssh-remote+%s.devpod/%s", workspace, folder)
145+
args = append(codePath, args...)
111146
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()
147+
log.Debugf("Run %s command %s %s", flavor.DisplayName(), args[0], strings.Join(args[1:], " "))
148+
out, err = exec.CommandContext(ctx, args[0], args[1:]...).CombinedOutput()
114149
if err != nil {
115150
return command.WrapCommandError(out, err)
116151
}
117152

118153
return nil
119154
}
120155

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 ""
156+
func existsInFlatpak(packageName string, log log.Logger) bool {
157+
if err := exec.Command("flatpak", "info", packageName).Run(); err == nil {
158+
return true
159+
} else {
160+
log.Debugf("Flatpak command for %s returned: %s", packageName, err)
130161
}
162+
return false
163+
}
131164

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 ""
165+
func getCommandArgs(execName, macOSPath, flatpakPackage string, log log.Logger) []string {
166+
if command.Exists(execName) {
167+
return []string{execName}
140168
}
141169

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 ""
170+
if runtime.GOOS == "darwin" && command.Exists(macOSPath) {
171+
return []string{macOSPath}
150172
}
151173

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 ""
174+
if runtime.GOOS == "linux" && flatpakPackage != "" && command.Exists("flatpak") && existsInFlatpak(flatpakPackage, log) {
175+
return []string{"flatpak", "run", flatpakPackage}
160176
}
161177

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-
}
178+
return nil
179+
}
168180

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

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)