Skip to content

Commit

Permalink
feat: support opening projects in JetBrains IDEs (#186)
Browse files Browse the repository at this point in the history
Signed-off-by: Toma Puljak <[email protected]>
  • Loading branch information
Tpuljak authored Mar 13, 2024
1 parent 941db71 commit e190b05
Show file tree
Hide file tree
Showing 10 changed files with 385 additions and 123 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ daytona target set
Following the steps this command adds SSH machines to your targets.

__4. Choose Your Default IDE:__
The default setting for Daytona is VS Code locally. If you prefer, you can switch to VS Code - Browser or any IDE from the JetBrains portfolio (Contributions welcome here!) using the command:
The default setting for Daytona is VS Code locally. If you prefer, you can switch to VS Code - Browser or any IDE from the JetBrains portfolio using the command:
```bash
daytona ide
```
Expand Down
13 changes: 11 additions & 2 deletions cmd/daytona/config/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

package config

import "github.com/daytonaio/daytona/pkg/os"
import (
"github.com/daytonaio/daytona/internal/jetbrains"
"github.com/daytonaio/daytona/pkg/os"
)

func GetBinaryUrls() map[os.OperatingSystem]string {
return map[os.OperatingSystem]string{
Expand All @@ -17,10 +20,16 @@ func GetBinaryUrls() map[os.OperatingSystem]string {
}

func GetIdeList() []Ide {
return []Ide{
ides := []Ide{
{"vscode", "VS Code"},
{"browser", "VS Code - Browser"},
}

for id, ide := range jetbrains.GetIdes() {
ides = append(ides, Ide{string(id), ide.Name})
}

return ides
}

func GetGitProviderList() []GitProvider {
Expand Down
89 changes: 89 additions & 0 deletions internal/jetbrains/ides.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2024 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0

package jetbrains

func GetIdes() map[Id]Ide {
return map[Id]Ide{
CLion: clion,
IntelliJ: intellij,
GoLand: goland,
PyCharm: pycharm,
PhpStorm: phpstorm,
WebStorm: webstorm,
Rider: rider,
RubyMine: rubymine,
}
}

var clion = Ide{
Name: "CLion",
Version: "2023.2.2",
UrlTemplates: UrlTemplates{
Amd64: "https://download.jetbrains.com/cpp/CLion-%s.tar.gz",
Arm64: "https://download.jetbrains.com/cpp/CLion-%s-aarch64.tar.gz",
},
}

var intellij = Ide{
Name: "IntelliJ IDEA Ultimate",
Version: "2023.2.2",
UrlTemplates: UrlTemplates{
Amd64: "https://download.jetbrains.com/idea/ideaIU-%s.tar.gz",
Arm64: "https://download.jetbrains.com/idea/ideaIU-%s-aarch64.tar.gz",
},
}

var goland = Ide{
Name: "GoLand",
Version: "2023.2.2",
UrlTemplates: UrlTemplates{
Amd64: "https://download.jetbrains.com/go/goland-%s.tar.gz",
Arm64: "https://download.jetbrains.com/go/goland-%s-aarch64.tar.gz",
},
}

var pycharm = Ide{
Name: "PyCharm Professional",
Version: "2023.2.2",
UrlTemplates: UrlTemplates{
Amd64: "https://download.jetbrains.com/python/pycharm-professional-%s.tar.gz",
Arm64: "https://download.jetbrains.com/python/pycharm-professional-%s-aarch64.tar.gz",
},
}

var phpstorm = Ide{
Name: "PhpStorm",
Version: "2023.2.2",
UrlTemplates: UrlTemplates{
Amd64: "https://download.jetbrains.com/webide/PhpStorm-%s.tar.gz",
Arm64: "https://download.jetbrains.com/webide/PhpStorm-%s-aarch64.tar.gz",
},
}

var webstorm = Ide{
Name: "WebStorm",
Version: "2023.2.2",
UrlTemplates: UrlTemplates{
Amd64: "https://download.jetbrains.com/webstorm/WebStorm-%s.tar.gz",
Arm64: "https://download.jetbrains.com/webstorm/WebStorm-%s-aarch64.tar.gz",
},
}

var rider = Ide{
Name: "Rider",
Version: "2023.2.2",
UrlTemplates: UrlTemplates{
Amd64: "https://download.jetbrains.com/rider/JetBrains.Rider-%s.tar.gz",
Arm64: "https://download.jetbrains.com/rider/JetBrains.Rider-%s-aarch64.tar.gz",
},
}

var rubymine = Ide{
Name: "RubyMine",
Version: "2023.2.2",
UrlTemplates: UrlTemplates{
Amd64: "https://download.jetbrains.com/ruby/RubyMine-%s.tar.gz",
Arm64: "https://download.jetbrains.com/ruby/RubyMine-%s-aarch64.tar.gz",
},
}
28 changes: 28 additions & 0 deletions internal/jetbrains/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright 2024 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0

package jetbrains

type Ide struct {
Name string
Version string
UrlTemplates UrlTemplates
}

type UrlTemplates struct {
Amd64 string
Arm64 string
}

type Id string

const (
CLion Id = "clion"
IntelliJ Id = "intellij"
GoLand Id = "goland"
PyCharm Id = "pycharm"
PhpStorm Id = "phpstorm"
WebStorm Id = "webstorm"
Rider Id = "rider"
RubyMine Id = "rubymine"
)
21 changes: 21 additions & 0 deletions internal/util/remote_os.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2024 Daytona Platforms Inc.
// SPDX-License-Identifier: Apache-2.0

package util

import (
"os/exec"

"github.com/daytonaio/daytona/pkg/os"
)

func GetRemoteOS(remote string) (*os.OperatingSystem, error) {
unameCmd := exec.Command("ssh", remote, "uname -a")

output, err := unameCmd.Output()
if err != nil {
return nil, err
}

return os.OSFromUnameA(string(output))
}
127 changes: 14 additions & 113 deletions pkg/cmd/workspace/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,15 @@ import (
"context"
"errors"
"fmt"
"io"
"os/exec"
"path"

"github.com/daytonaio/daytona/cmd/daytona/config"
"github.com/daytonaio/daytona/internal/util"
"github.com/daytonaio/daytona/internal/jetbrains"
"github.com/daytonaio/daytona/internal/util/apiclient"
"github.com/daytonaio/daytona/internal/util/apiclient/server"
"github.com/daytonaio/daytona/pkg/ports"
"github.com/daytonaio/daytona/pkg/ide"
view_util "github.com/daytonaio/daytona/pkg/views/util"
"github.com/daytonaio/daytona/pkg/views/workspace/selection"

"github.com/pkg/browser"
log "github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
Expand Down Expand Up @@ -92,7 +88,7 @@ var CodeCmd = &cobra.Command{

view_util.RenderInfoMessage(fmt.Sprintf("Opening the workspace project '%s' in your preferred IDE.", projectName))

openIDE(ideId, activeProfile, workspaceId, projectName)
log.Fatal(openIDE(ideId, activeProfile, workspaceId, projectName))
},
}

Expand Down Expand Up @@ -122,115 +118,20 @@ func selectWorkspaceProject(workspaceId string, profile *config.Profile) (*strin
return nil, errors.New("no projects found in workspace")
}

func openIDE(ideId string, activeProfile config.Profile, workspaceId string, projectName string) {
if ideId == "browser" {
err := openBrowserIDE(activeProfile, workspaceId, projectName)
if err != nil {
log.Fatal(err)
func openIDE(ideId string, activeProfile config.Profile, workspaceId string, projectName string) error {
switch ideId {
case "vscode":
return ide.OpenVSCode(activeProfile, workspaceId, projectName)
case "browser":
return ide.OpenBrowserIDE(activeProfile, workspaceId, projectName)
default:
_, ok := jetbrains.GetIdes()[jetbrains.Id(ideId)]
if ok {
return ide.OpenJetbrainsIDE(activeProfile, ideId, workspaceId, projectName)
}
return
}

openVSCode(activeProfile, workspaceId, projectName)
}

func openVSCode(activeProfile config.Profile, workspaceId string, projectName string) {
err := config.EnsureSshConfigEntryAdded(activeProfile.Id, workspaceId, projectName)
if err != nil {
log.Fatal(err)
}

checkAndAlertVSCodeInstalled()

projectHostname := config.GetProjectHostname(activeProfile.Id, workspaceId, projectName)

commandArgument := fmt.Sprintf("vscode-remote://ssh-remote+%s/%s", projectHostname, path.Join("/workspaces", projectName))

var vscCommand *exec.Cmd = exec.Command("code", "--folder-uri", commandArgument)

err = vscCommand.Run()

if err != nil {
log.Fatal(err.Error())
}
}

func openBrowserIDE(activeProfile config.Profile, workspaceId string, projectName string) error {
// Download and start IDE
err := config.EnsureSshConfigEntryAdded(activeProfile.Id, workspaceId, projectName)
if err != nil {
return err
}

view_util.RenderInfoMessageBold("Downloading OpenVSCode Server...")
projectHostname := config.GetProjectHostname(activeProfile.Id, workspaceId, projectName)

installServerCommand := exec.Command("ssh", projectHostname, "curl -fsSL https://download.daytona.io/daytona/get-openvscode-server.sh | sh")
installServerCommand.Stdout = io.Writer(&util.DebugLogWriter{})
installServerCommand.Stderr = io.Writer(&util.DebugLogWriter{})

err = installServerCommand.Run()
if err != nil {
return err
}

view_util.RenderInfoMessageBold("Starting OpenVSCode Server...")

go func() {
startServerCommand := exec.CommandContext(context.Background(), "ssh", projectHostname, startVSCodeServerCommand)
startServerCommand.Stdout = io.Writer(&util.DebugLogWriter{})
startServerCommand.Stderr = io.Writer(&util.DebugLogWriter{})

err = startServerCommand.Run()
if err != nil {
log.Fatal(err)
}
}()

// Forward IDE port
browserPort, errChan := ports.ForwardPort(workspaceId, projectName, 63000)
if browserPort == nil {
if err := <-errChan; err != nil {
return err
}
}

view_util.RenderInfoMessageBold(fmt.Sprintf("Forwarded %s IDE port to %d.\nOpening browser...", projectName, *browserPort))

err = browser.OpenURL(fmt.Sprintf("http://localhost:%d", *browserPort))
if err != nil {
log.Error("Error opening URL: " + err.Error())
}

for {
err := <-errChan
if err != nil {
// Log only in debug mode
// Connection errors to the forwarded port should not exit the process
log.Debug(err)
}
}
}

const startVSCodeServerCommand = "$HOME/vscode-server/bin/openvscode-server --start-server --port=63000 --host=0.0.0.0 --without-connection-token --disable-workspace-trust --default-folder=$DAYTONA_WS_DIR"

func checkAndAlertVSCodeInstalled() {
if err := isVSCodeInstalled(); err != nil {
redBold := "\033[1;31m" // ANSI escape code for red and bold
reset := "\033[0m" // ANSI escape code to reset text formatting

errorMessage := "Please install Visual Studio Code and ensure it's in your PATH. "
infoMessage := "More information on: 'https://code.visualstudio.com/docs/editor/command-line#_launching-from-command-line'"

log.Error(redBold + errorMessage + reset + infoMessage)

return
}
}

func isVSCodeInstalled() error {
_, err := exec.LookPath("code")
return err
return errors.New("invalid IDE")
}

var ideFlag string
Expand Down
Loading

0 comments on commit e190b05

Please sign in to comment.