Skip to content

Commit a442ad7

Browse files
committed
feat(VSCode): add support for IDE inside Flatpak
1 parent fe07d22 commit a442ad7

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
@@ -177,6 +177,7 @@ func (cmd *UpCmd) Run(
177177
}
178178

179179
// configure container ssh
180+
var sshConfigPath string
180181
if cmd.ConfigureSSH {
181182
devPodHome := ""
182183
envDevPodHome, ok := os.LookupEnv("DEVPOD_HOME")
@@ -185,7 +186,7 @@ func (cmd *UpCmd) Run(
185186
}
186187
setupGPGAgentForwarding := cmd.GPGAgentForwarding || devPodConfig.ContextOption(config.ContextOptionGPGAgentForwarding) == "true"
187188

188-
err = configureSSH(devPodConfig, client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome)
189+
sshConfigPath, err = configureSSH(devPodConfig, client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome)
189190
if err != nil {
190191
return err
191192
}
@@ -218,6 +219,7 @@ func (cmd *UpCmd) Run(
218219
result.SubstitutionContext.ContainerWorkspaceFolder,
219220
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
220221
vscode.FlavorStable,
222+
sshConfigPath,
221223
log,
222224
)
223225
case string(config.IDEVSCodeInsiders):
@@ -227,6 +229,7 @@ func (cmd *UpCmd) Run(
227229
result.SubstitutionContext.ContainerWorkspaceFolder,
228230
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
229231
vscode.FlavorInsiders,
232+
sshConfigPath,
230233
log,
231234
)
232235
case string(config.IDECursor):
@@ -236,6 +239,7 @@ func (cmd *UpCmd) Run(
236239
result.SubstitutionContext.ContainerWorkspaceFolder,
237240
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
238241
vscode.FlavorCursor,
242+
sshConfigPath,
239243
log,
240244
)
241245
case string(config.IDECodium):
@@ -245,6 +249,17 @@ func (cmd *UpCmd) Run(
245249
result.SubstitutionContext.ContainerWorkspaceFolder,
246250
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
247251
vscode.FlavorCodium,
252+
sshConfigPath,
253+
log,
254+
)
255+
case string(config.IDECodiumInsiders):
256+
return vscode.Open(
257+
ctx,
258+
client.Workspace(),
259+
result.SubstitutionContext.ContainerWorkspaceFolder,
260+
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
261+
vscode.FlavorCodiumInsiders,
262+
sshConfigPath,
248263
log,
249264
)
250265
case string(config.IDEPositron):
@@ -254,6 +269,7 @@ func (cmd *UpCmd) Run(
254269
result.SubstitutionContext.ContainerWorkspaceFolder,
255270
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
256271
vscode.FlavorPositron,
272+
sshConfigPath,
257273
log,
258274
)
259275
case string(config.IDEOpenVSCode):
@@ -907,10 +923,10 @@ func startBrowserTunnel(
907923
return nil
908924
}
909925

910-
func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) error {
926+
func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) (string, error) {
911927
path, err := devssh.ResolveSSHConfigPath(sshConfigPath)
912928
if err != nil {
913-
return errors.Wrap(err, "Invalid ssh config path")
929+
return "", errors.Wrap(err, "Invalid ssh config path")
914930
}
915931
sshConfigPath = path
916932

@@ -925,10 +941,10 @@ func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfi
925941
log.Default,
926942
)
927943
if err != nil {
928-
return err
944+
return "", err
929945
}
930946

931-
return nil
947+
return sshConfigPath, nil
932948
}
933949

934950
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
@@ -5,16 +5,24 @@ import (
55
"fmt"
66
"os/exec"
77
"runtime"
8+
"slices"
89
"strings"
910

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

15-
func Open(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, log log.Logger) error {
16+
const (
17+
FlatpakStable string = "com.visualstudio.code"
18+
FlatpakInsiders string = "com.visualstudio.code.insiders"
19+
FlatpakCodium string = "com.vscodium.codium"
20+
FlatpakCodiumInsiders string = "com.vscodium.codium-insiders"
21+
)
22+
23+
func Open(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, sshConfigPath string, log log.Logger) error {
1624
log.Infof("Starting %s...", flavor)
17-
err := openViaCLI(ctx, workspace, folder, newWindow, flavor, log)
25+
err := openViaCLI(ctx, workspace, folder, newWindow, flavor, sshConfigPath, log)
1826
if err != nil {
1927
log.Debugf("Error opening %s via cli: %v", flavor, err)
2028
} else {
@@ -37,6 +45,8 @@ func openViaBrowser(workspace, folder string, newWindow bool, flavor Flavor, log
3745
protocol = `positron://`
3846
case FlavorCodium:
3947
protocol = `codium://`
48+
case FlavorCodiumInsiders:
49+
protocol = `codium-insiders://`
4050
}
4151

4252
openURL := protocol + `vscode-remote/ssh-remote+` + workspace + `.devpod/` + folder
@@ -54,20 +64,44 @@ func openViaBrowser(workspace, folder string, newWindow bool, flavor Flavor, log
5464
return nil
5565
}
5666

57-
func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, log log.Logger) error {
67+
func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, sshConfigPath string, log log.Logger) error {
5868
// try to find code cli
59-
codePath := findCLI(flavor)
60-
if codePath == "" {
69+
codePath := findCLI(flavor, log)
70+
if codePath == nil {
6171
return fmt.Errorf("couldn't find the %s binary", flavor)
6272
}
6373

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

69102
// make sure ms-vscode-remote.remote-ssh is installed
70-
out, err := exec.Command(codePath, "--list-extensions").Output()
103+
listArgs := append(codePath, "--list-extensions")
104+
out, err := exec.Command(listArgs[0], listArgs[1:]...).Output()
71105
if err != nil {
72106
return command.WrapCommandError(out, err)
73107
}
@@ -84,9 +118,9 @@ func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, f
84118

85119
// install remote-ssh extension
86120
if !found {
87-
args := []string{"--install-extension", sshExtension}
88-
log.Debugf("Run vscode command %s %s", codePath, strings.Join(args, " "))
89-
out, err := exec.CommandContext(ctx, codePath, args...).Output()
121+
args := append(codePath, "--install-extension", sshExtension)
122+
log.Debugf("Run vscode command %s %s", args[0], strings.Join(args[1:], " "))
123+
out, err := exec.CommandContext(ctx, args[0], args[1:]...).Output()
90124
if err != nil {
91125
return fmt.Errorf("install ssh extension: %w", command.WrapCommandError(out, err))
92126
}
@@ -104,66 +138,56 @@ func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, f
104138
}
105139
// Needs to be separated by `=` because of windows
106140
folderUriArg := fmt.Sprintf("--folder-uri=vscode-remote://ssh-remote+%s.devpod/%s", workspace, folder)
141+
args = append(codePath, args...)
107142
args = append(args, folderUriArg)
108-
log.Debugf("Run %s command %s %s", flavor, codePath, strings.Join(args, " "))
109-
out, err = exec.CommandContext(ctx, codePath, args...).CombinedOutput()
143+
log.Debugf("Run %s command %s %s", flavor, args[0], strings.Join(args[1:], " "))
144+
out, err = exec.CommandContext(ctx, args[0], args[1:]...).CombinedOutput()
110145
if err != nil {
111146
return command.WrapCommandError(out, err)
112147
}
113148

114149
return nil
115150
}
116151

117-
func findCLI(flavor Flavor) string {
118-
if flavor == FlavorStable {
119-
if command.Exists("code") {
120-
return "code"
121-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code") {
122-
return "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
123-
}
124-
125-
return ""
152+
func existsInFlatpak(packageName string, log log.Logger) bool {
153+
if err := exec.Command("flatpak", "info", packageName).Run(); err == nil {
154+
return true
155+
} else {
156+
log.Debugf("Flatpak command for %s returned: %s", packageName, err)
126157
}
158+
return false
159+
}
127160

128-
if flavor == FlavorInsiders {
129-
if command.Exists("code-insiders") {
130-
return "code-insiders"
131-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code") {
132-
return "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"
133-
}
134-
135-
return ""
161+
func getCommandArgs(execName, macOSPath, flatpakPackage string, log log.Logger) []string {
162+
if command.Exists(execName) {
163+
return []string{execName}
136164
}
137165

138-
if flavor == FlavorCursor {
139-
if command.Exists("cursor") {
140-
return "cursor"
141-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Cursor.app/Contents/Resources/app/bin/cursor") {
142-
return "/Applications/Cursor.app/Contents/Resources/app/bin/cursor"
143-
}
144-
145-
return ""
166+
if runtime.GOOS == "darwin" && command.Exists(macOSPath) {
167+
return []string{macOSPath}
146168
}
147169

148-
if flavor == FlavorPositron {
149-
if command.Exists("positron") {
150-
return "positron"
151-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Positron.app/Contents/Resources/app/bin/positron") {
152-
return "/Applications/Positron.app/Contents/Resources/app/bin/positron"
153-
}
154-
155-
return ""
170+
if runtime.GOOS == "linux" && flatpakPackage != "" && command.Exists("flatpak") && existsInFlatpak(flatpakPackage, log) {
171+
return []string{"flatpak", "run", flatpakPackage}
156172
}
157173

158-
if flavor == FlavorCodium {
159-
if command.Exists("codium") {
160-
return "codium"
161-
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Codium.app/Contents/Resources/app/bin/codium") {
162-
return "/Applications/Codium.app/Contents/Resources/app/bin/codium"
163-
}
174+
return nil
175+
}
164176

165-
return ""
177+
func findCLI(flavor Flavor, log log.Logger) []string {
178+
switch flavor {
179+
case FlavorStable:
180+
return getCommandArgs("code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", FlatpakStable, log)
181+
case FlavorInsiders:
182+
return getCommandArgs("code-insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", FlatpakInsiders, log)
183+
case FlavorCursor:
184+
return getCommandArgs("cursor", "/Applications/Cursor.app/Contents/Resources/app/bin/cursor", "", log)
185+
case FlavorPositron:
186+
return getCommandArgs("positron", "/Applications/Positron.app/Contents/Resources/app/bin/positron", "", log)
187+
case FlavorCodium:
188+
return getCommandArgs("codium", "/Applications/Codium.app/Contents/Resources/app/bin/codium", FlatpakCodium, log)
189+
case FlavorCodiumInsiders:
190+
return getCommandArgs("codium-insiders", "/Applications/CodiumInsiders.app/Contents/Resources/app/bin/codium-insiders", FlatpakCodiumInsiders, log)
166191
}
167-
168-
return ""
192+
return nil
169193
}

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
var Options = ide.Options{

0 commit comments

Comments
 (0)