Skip to content

Commit

Permalink
feat(VSCode): add support for IDE inside Flatpak
Browse files Browse the repository at this point in the history
  • Loading branch information
acolombier committed Jan 19, 2025
1 parent 0c6b3c3 commit 59cb805
Show file tree
Hide file tree
Showing 5 changed files with 116 additions and 65 deletions.
26 changes: 21 additions & 5 deletions cmd/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ func (cmd *UpCmd) Run(
}

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

err = configureSSH(devPodConfig, client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome)
sshConfigPath, err = configureSSH(devPodConfig, client, cmd.SSHConfigPath, user, workdir, setupGPGAgentForwarding, devPodHome)
if err != nil {
return err
}
Expand Down Expand Up @@ -238,6 +239,7 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorStable,
sshConfigPath,
log,
)
case string(config.IDEVSCodeInsiders):
Expand All @@ -247,6 +249,7 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorInsiders,
sshConfigPath,
log,
)
case string(config.IDECursor):
Expand All @@ -256,6 +259,7 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorCursor,
sshConfigPath,
log,
)
case string(config.IDECodium):
Expand All @@ -265,6 +269,17 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorCodium,
sshConfigPath,
log,
)
case string(config.IDECodiumInsiders):
return vscode.Open(
ctx,
client.Workspace(),
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorCodiumInsiders,
sshConfigPath,
log,
)
case string(config.IDEPositron):
Expand All @@ -274,6 +289,7 @@ func (cmd *UpCmd) Run(
result.SubstitutionContext.ContainerWorkspaceFolder,
vscode.Options.GetValue(ideConfig.Options, vscode.OpenNewWindow) == "true",
vscode.FlavorPositron,
sshConfigPath,
log,
)
case string(config.IDEOpenVSCode):
Expand Down Expand Up @@ -995,10 +1011,10 @@ func startBrowserTunnel(
return nil
}

func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) error {
func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfigPath, user, workdir string, gpgagent bool, devPodHome string) (string, error) {
path, err := devssh.ResolveSSHConfigPath(sshConfigPath)
if err != nil {
return errors.Wrap(err, "Invalid ssh config path")
return "", errors.Wrap(err, "Invalid ssh config path")
}
sshConfigPath = path

Expand All @@ -1013,10 +1029,10 @@ func configureSSH(c *config.Config, client client2.BaseWorkspaceClient, sshConfi
log.Default,
)
if err != nil {
return err
return "", err
}

return nil
return sshConfigPath, nil
}

func mergeDevPodUpOptions(baseOptions *provider2.CLIOptions) error {
Expand Down
1 change: 1 addition & 0 deletions pkg/config/ide.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ const (
IDEPositron IDE = "positron"
IDEMarimo IDE = "marimo"
IDECodium IDE = "codium"
IDECodiumInsiders IDE = "codium-insiders"
IDEZed IDE = "zed"
)
7 changes: 7 additions & 0 deletions pkg/ide/ideparse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,13 @@ var AllowedIDEs = []AllowedIDE{
Icon: "https://devpod.sh/assets/codium.svg",
Experimental: true,
},
{
Name: config.IDECodiumInsiders,
DisplayName: "Codium Insiders",
Options: vscode.Options,
Icon: "https://devpod.sh/assets/codium_insiders.svg", // TODO to be uploaded
Experimental: true,
},
{
Name: config.IDEPositron,
DisplayName: "Positron",
Expand Down
136 changes: 81 additions & 55 deletions pkg/ide/vscode/open.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,27 @@ import (
"fmt"
"os/exec"
"runtime"
"slices"
"strings"

"github.com/loft-sh/devpod/pkg/command"
"github.com/loft-sh/log"
"github.com/skratchdot/open-golang/open"
)

func Open(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, log log.Logger) error {
const (
FlatpakStable string = "com.visualstudio.code"
FlatpakInsiders string = "com.visualstudio.code.insiders"
FlatpakCodium string = "com.vscodium.codium"
FlatpakCodiumInsiders string = "com.vscodium.codium-insiders"
)

func Open(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, sshConfigPath string, log log.Logger) error {
log.Infof("Starting %s...", flavor.DisplayName())
cliErr := openViaCLI(ctx, workspace, folder, newWindow, flavor, log)
if cliErr == nil {
cliErr := openViaCLI(ctx, workspace, folder, newWindow, flavor, sshConfigPath, log)
if cliErr != nil {
log.Debugf("Error opening %s via cli: %v", flavor, cliErr)
} else {
return nil
}

Expand All @@ -41,6 +51,8 @@ func openViaBrowser(workspace, folder string, newWindow bool, flavor Flavor, log
protocol = `positron://`
case FlavorCodium:
protocol = `codium://`
case FlavorCodiumInsiders:
protocol = `codium-insiders://`
}

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

func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, log log.Logger) error {
func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, flavor Flavor, sshConfigPath string, log log.Logger) error {
// try to find code cli
codePath := findCLI(flavor)
if codePath == "" {
codePath := findCLI(flavor, log)
if codePath == nil {
return fmt.Errorf("couldn't find the %s binary", flavor)
}

if codePath[0] == "flatpak" {
log.Debugf("Running with Flatpak suing the package %s.", codePath[2])
out, err := exec.Command(codePath[0], "ps", "--columns=application").Output()
if err != nil {
return command.WrapCommandError(out, err)
}
splitted := strings.Split(string(out), "\n")
foundRunning := false
// Ignore the header
for _, str := range splitted[1:] {
if strings.TrimSpace(str) == codePath[2] {
foundRunning = true
break
}
}

if foundRunning {
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)
}

codePath = slices.Insert(codePath, 2, fmt.Sprintf("--filesystem=%s:ro", sshConfigPath))
}

sshExtension := "ms-vscode-remote.remote-ssh"
if flavor == FlavorCodium {
if flavor == FlavorCodium || flavor == FlavorCodiumInsiders {
sshExtension = "jeanp413.open-remote-ssh"
}

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

// install remote-ssh extension
if !found {
args := []string{"--install-extension", sshExtension}
log.Debugf("Run vscode command %s %s", codePath, strings.Join(args, " "))
out, err := exec.CommandContext(ctx, codePath, args...).Output()
args := append(codePath, "--install-extension", sshExtension)
log.Debugf("Run vscode command %s %s", args[0], strings.Join(args[1:], " "))
out, err := exec.CommandContext(ctx, args[0], args[1:]...).Output()
if err != nil {
return fmt.Errorf("install ssh extension: %w", command.WrapCommandError(out, err))
}
Expand All @@ -108,66 +144,56 @@ func openViaCLI(ctx context.Context, workspace, folder string, newWindow bool, f
}
// Needs to be separated by `=` because of windows
folderUriArg := fmt.Sprintf("--folder-uri=vscode-remote://ssh-remote+%s.devpod/%s", workspace, folder)
args = append(codePath, args...)
args = append(args, folderUriArg)
log.Debugf("Run %s command %s %s", flavor.DisplayName(), codePath, strings.Join(args, " "))
out, err = exec.CommandContext(ctx, codePath, args...).CombinedOutput()
log.Debugf("Run %s command %s %s", flavor.DisplayName(), args[0], strings.Join(args[1:], " "))
out, err = exec.CommandContext(ctx, args[0], args[1:]...).CombinedOutput()
if err != nil {
return command.WrapCommandError(out, err)
}

return nil
}

func findCLI(flavor Flavor) string {
if flavor == FlavorStable {
if command.Exists("code") {
return "code"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code") {
return "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code"
}

return ""
func existsInFlatpak(packageName string, log log.Logger) bool {
if err := exec.Command("flatpak", "info", packageName).Run(); err == nil {
return true
} else {
log.Debugf("Flatpak command for %s returned: %s", packageName, err)
}
return false
}

if flavor == FlavorInsiders {
if command.Exists("code-insiders") {
return "code-insiders"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code") {
return "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code"
}

return ""
func getCommandArgs(execName, macOSPath, flatpakPackage string, log log.Logger) []string {
if command.Exists(execName) {
return []string{execName}
}

if flavor == FlavorCursor {
if command.Exists("cursor") {
return "cursor"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Cursor.app/Contents/Resources/app/bin/cursor") {
return "/Applications/Cursor.app/Contents/Resources/app/bin/cursor"
}

return ""
if runtime.GOOS == "darwin" && command.Exists(macOSPath) {
return []string{macOSPath}
}

if flavor == FlavorPositron {
if command.Exists("positron") {
return "positron"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Positron.app/Contents/Resources/app/bin/positron") {
return "/Applications/Positron.app/Contents/Resources/app/bin/positron"
}

return ""
if runtime.GOOS == "linux" && flatpakPackage != "" && command.Exists("flatpak") && existsInFlatpak(flatpakPackage, log) {
return []string{"flatpak", "run", flatpakPackage}
}

if flavor == FlavorCodium {
if command.Exists("codium") {
return "codium"
} else if runtime.GOOS == "darwin" && command.Exists("/Applications/Codium.app/Contents/Resources/app/bin/codium") {
return "/Applications/Codium.app/Contents/Resources/app/bin/codium"
}
return nil
}

return ""
func findCLI(flavor Flavor, log log.Logger) []string {
switch flavor {
case FlavorStable:
return getCommandArgs("code", "/Applications/Visual Studio Code.app/Contents/Resources/app/bin/code", FlatpakStable, log)
case FlavorInsiders:
return getCommandArgs("code-insiders", "/Applications/Visual Studio Code - Insiders.app/Contents/Resources/app/bin/code", FlatpakInsiders, log)
case FlavorCursor:
return getCommandArgs("cursor", "/Applications/Cursor.app/Contents/Resources/app/bin/cursor", "", log)
case FlavorPositron:
return getCommandArgs("positron", "/Applications/Positron.app/Contents/Resources/app/bin/positron", "", log)
case FlavorCodium:
return getCommandArgs("codium", "/Applications/Codium.app/Contents/Resources/app/bin/codium", FlatpakCodium, log)
case FlavorCodiumInsiders:
return getCommandArgs("codium-insiders", "/Applications/CodiumInsiders.app/Contents/Resources/app/bin/codium-insiders", FlatpakCodiumInsiders, log)
}

return ""
return nil
}
11 changes: 6 additions & 5 deletions pkg/ide/vscode/vscode.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,12 @@ const (
type Flavor string

const (
FlavorStable Flavor = "stable"
FlavorInsiders Flavor = "insiders"
FlavorCursor Flavor = "cursor"
FlavorPositron Flavor = "positron"
FlavorCodium Flavor = "codium"
FlavorStable Flavor = "stable"
FlavorInsiders Flavor = "insiders"
FlavorCursor Flavor = "cursor"
FlavorPositron Flavor = "positron"
FlavorCodium Flavor = "codium"
FlavorCodiumInsiders Flavor = "codium-insiders"
)

func (f Flavor) DisplayName() string {
Expand Down

0 comments on commit 59cb805

Please sign in to comment.