Skip to content

Commit e190b05

Browse files
authored
feat: support opening projects in JetBrains IDEs (#186)
Signed-off-by: Toma Puljak <[email protected]>
1 parent 941db71 commit e190b05

File tree

10 files changed

+385
-123
lines changed

10 files changed

+385
-123
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ daytona target set
146146
Following the steps this command adds SSH machines to your targets.
147147

148148
__4. Choose Your Default IDE:__
149-
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:
149+
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:
150150
```bash
151151
daytona ide
152152
```

cmd/daytona/config/const.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33

44
package config
55

6-
import "github.com/daytonaio/daytona/pkg/os"
6+
import (
7+
"github.com/daytonaio/daytona/internal/jetbrains"
8+
"github.com/daytonaio/daytona/pkg/os"
9+
)
710

811
func GetBinaryUrls() map[os.OperatingSystem]string {
912
return map[os.OperatingSystem]string{
@@ -17,10 +20,16 @@ func GetBinaryUrls() map[os.OperatingSystem]string {
1720
}
1821

1922
func GetIdeList() []Ide {
20-
return []Ide{
23+
ides := []Ide{
2124
{"vscode", "VS Code"},
2225
{"browser", "VS Code - Browser"},
2326
}
27+
28+
for id, ide := range jetbrains.GetIdes() {
29+
ides = append(ides, Ide{string(id), ide.Name})
30+
}
31+
32+
return ides
2433
}
2534

2635
func GetGitProviderList() []GitProvider {

internal/jetbrains/ides.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2024 Daytona Platforms Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package jetbrains
5+
6+
func GetIdes() map[Id]Ide {
7+
return map[Id]Ide{
8+
CLion: clion,
9+
IntelliJ: intellij,
10+
GoLand: goland,
11+
PyCharm: pycharm,
12+
PhpStorm: phpstorm,
13+
WebStorm: webstorm,
14+
Rider: rider,
15+
RubyMine: rubymine,
16+
}
17+
}
18+
19+
var clion = Ide{
20+
Name: "CLion",
21+
Version: "2023.2.2",
22+
UrlTemplates: UrlTemplates{
23+
Amd64: "https://download.jetbrains.com/cpp/CLion-%s.tar.gz",
24+
Arm64: "https://download.jetbrains.com/cpp/CLion-%s-aarch64.tar.gz",
25+
},
26+
}
27+
28+
var intellij = Ide{
29+
Name: "IntelliJ IDEA Ultimate",
30+
Version: "2023.2.2",
31+
UrlTemplates: UrlTemplates{
32+
Amd64: "https://download.jetbrains.com/idea/ideaIU-%s.tar.gz",
33+
Arm64: "https://download.jetbrains.com/idea/ideaIU-%s-aarch64.tar.gz",
34+
},
35+
}
36+
37+
var goland = Ide{
38+
Name: "GoLand",
39+
Version: "2023.2.2",
40+
UrlTemplates: UrlTemplates{
41+
Amd64: "https://download.jetbrains.com/go/goland-%s.tar.gz",
42+
Arm64: "https://download.jetbrains.com/go/goland-%s-aarch64.tar.gz",
43+
},
44+
}
45+
46+
var pycharm = Ide{
47+
Name: "PyCharm Professional",
48+
Version: "2023.2.2",
49+
UrlTemplates: UrlTemplates{
50+
Amd64: "https://download.jetbrains.com/python/pycharm-professional-%s.tar.gz",
51+
Arm64: "https://download.jetbrains.com/python/pycharm-professional-%s-aarch64.tar.gz",
52+
},
53+
}
54+
55+
var phpstorm = Ide{
56+
Name: "PhpStorm",
57+
Version: "2023.2.2",
58+
UrlTemplates: UrlTemplates{
59+
Amd64: "https://download.jetbrains.com/webide/PhpStorm-%s.tar.gz",
60+
Arm64: "https://download.jetbrains.com/webide/PhpStorm-%s-aarch64.tar.gz",
61+
},
62+
}
63+
64+
var webstorm = Ide{
65+
Name: "WebStorm",
66+
Version: "2023.2.2",
67+
UrlTemplates: UrlTemplates{
68+
Amd64: "https://download.jetbrains.com/webstorm/WebStorm-%s.tar.gz",
69+
Arm64: "https://download.jetbrains.com/webstorm/WebStorm-%s-aarch64.tar.gz",
70+
},
71+
}
72+
73+
var rider = Ide{
74+
Name: "Rider",
75+
Version: "2023.2.2",
76+
UrlTemplates: UrlTemplates{
77+
Amd64: "https://download.jetbrains.com/rider/JetBrains.Rider-%s.tar.gz",
78+
Arm64: "https://download.jetbrains.com/rider/JetBrains.Rider-%s-aarch64.tar.gz",
79+
},
80+
}
81+
82+
var rubymine = Ide{
83+
Name: "RubyMine",
84+
Version: "2023.2.2",
85+
UrlTemplates: UrlTemplates{
86+
Amd64: "https://download.jetbrains.com/ruby/RubyMine-%s.tar.gz",
87+
Arm64: "https://download.jetbrains.com/ruby/RubyMine-%s-aarch64.tar.gz",
88+
},
89+
}

internal/jetbrains/types.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright 2024 Daytona Platforms Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package jetbrains
5+
6+
type Ide struct {
7+
Name string
8+
Version string
9+
UrlTemplates UrlTemplates
10+
}
11+
12+
type UrlTemplates struct {
13+
Amd64 string
14+
Arm64 string
15+
}
16+
17+
type Id string
18+
19+
const (
20+
CLion Id = "clion"
21+
IntelliJ Id = "intellij"
22+
GoLand Id = "goland"
23+
PyCharm Id = "pycharm"
24+
PhpStorm Id = "phpstorm"
25+
WebStorm Id = "webstorm"
26+
Rider Id = "rider"
27+
RubyMine Id = "rubymine"
28+
)

internal/util/remote_os.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright 2024 Daytona Platforms Inc.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package util
5+
6+
import (
7+
"os/exec"
8+
9+
"github.com/daytonaio/daytona/pkg/os"
10+
)
11+
12+
func GetRemoteOS(remote string) (*os.OperatingSystem, error) {
13+
unameCmd := exec.Command("ssh", remote, "uname -a")
14+
15+
output, err := unameCmd.Output()
16+
if err != nil {
17+
return nil, err
18+
}
19+
20+
return os.OSFromUnameA(string(output))
21+
}

pkg/cmd/workspace/code.go

Lines changed: 14 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,15 @@ import (
77
"context"
88
"errors"
99
"fmt"
10-
"io"
11-
"os/exec"
12-
"path"
1310

1411
"github.com/daytonaio/daytona/cmd/daytona/config"
15-
"github.com/daytonaio/daytona/internal/util"
12+
"github.com/daytonaio/daytona/internal/jetbrains"
1613
"github.com/daytonaio/daytona/internal/util/apiclient"
1714
"github.com/daytonaio/daytona/internal/util/apiclient/server"
18-
"github.com/daytonaio/daytona/pkg/ports"
15+
"github.com/daytonaio/daytona/pkg/ide"
1916
view_util "github.com/daytonaio/daytona/pkg/views/util"
2017
"github.com/daytonaio/daytona/pkg/views/workspace/selection"
2118

22-
"github.com/pkg/browser"
2319
log "github.com/sirupsen/logrus"
2420
"github.com/spf13/cobra"
2521
)
@@ -92,7 +88,7 @@ var CodeCmd = &cobra.Command{
9288

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

95-
openIDE(ideId, activeProfile, workspaceId, projectName)
91+
log.Fatal(openIDE(ideId, activeProfile, workspaceId, projectName))
9692
},
9793
}
9894

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

125-
func openIDE(ideId string, activeProfile config.Profile, workspaceId string, projectName string) {
126-
if ideId == "browser" {
127-
err := openBrowserIDE(activeProfile, workspaceId, projectName)
128-
if err != nil {
129-
log.Fatal(err)
121+
func openIDE(ideId string, activeProfile config.Profile, workspaceId string, projectName string) error {
122+
switch ideId {
123+
case "vscode":
124+
return ide.OpenVSCode(activeProfile, workspaceId, projectName)
125+
case "browser":
126+
return ide.OpenBrowserIDE(activeProfile, workspaceId, projectName)
127+
default:
128+
_, ok := jetbrains.GetIdes()[jetbrains.Id(ideId)]
129+
if ok {
130+
return ide.OpenJetbrainsIDE(activeProfile, ideId, workspaceId, projectName)
130131
}
131-
return
132-
}
133-
134-
openVSCode(activeProfile, workspaceId, projectName)
135-
}
136-
137-
func openVSCode(activeProfile config.Profile, workspaceId string, projectName string) {
138-
err := config.EnsureSshConfigEntryAdded(activeProfile.Id, workspaceId, projectName)
139-
if err != nil {
140-
log.Fatal(err)
141132
}
142133

143-
checkAndAlertVSCodeInstalled()
144-
145-
projectHostname := config.GetProjectHostname(activeProfile.Id, workspaceId, projectName)
146-
147-
commandArgument := fmt.Sprintf("vscode-remote://ssh-remote+%s/%s", projectHostname, path.Join("/workspaces", projectName))
148-
149-
var vscCommand *exec.Cmd = exec.Command("code", "--folder-uri", commandArgument)
150-
151-
err = vscCommand.Run()
152-
153-
if err != nil {
154-
log.Fatal(err.Error())
155-
}
156-
}
157-
158-
func openBrowserIDE(activeProfile config.Profile, workspaceId string, projectName string) error {
159-
// Download and start IDE
160-
err := config.EnsureSshConfigEntryAdded(activeProfile.Id, workspaceId, projectName)
161-
if err != nil {
162-
return err
163-
}
164-
165-
view_util.RenderInfoMessageBold("Downloading OpenVSCode Server...")
166-
projectHostname := config.GetProjectHostname(activeProfile.Id, workspaceId, projectName)
167-
168-
installServerCommand := exec.Command("ssh", projectHostname, "curl -fsSL https://download.daytona.io/daytona/get-openvscode-server.sh | sh")
169-
installServerCommand.Stdout = io.Writer(&util.DebugLogWriter{})
170-
installServerCommand.Stderr = io.Writer(&util.DebugLogWriter{})
171-
172-
err = installServerCommand.Run()
173-
if err != nil {
174-
return err
175-
}
176-
177-
view_util.RenderInfoMessageBold("Starting OpenVSCode Server...")
178-
179-
go func() {
180-
startServerCommand := exec.CommandContext(context.Background(), "ssh", projectHostname, startVSCodeServerCommand)
181-
startServerCommand.Stdout = io.Writer(&util.DebugLogWriter{})
182-
startServerCommand.Stderr = io.Writer(&util.DebugLogWriter{})
183-
184-
err = startServerCommand.Run()
185-
if err != nil {
186-
log.Fatal(err)
187-
}
188-
}()
189-
190-
// Forward IDE port
191-
browserPort, errChan := ports.ForwardPort(workspaceId, projectName, 63000)
192-
if browserPort == nil {
193-
if err := <-errChan; err != nil {
194-
return err
195-
}
196-
}
197-
198-
view_util.RenderInfoMessageBold(fmt.Sprintf("Forwarded %s IDE port to %d.\nOpening browser...", projectName, *browserPort))
199-
200-
err = browser.OpenURL(fmt.Sprintf("http://localhost:%d", *browserPort))
201-
if err != nil {
202-
log.Error("Error opening URL: " + err.Error())
203-
}
204-
205-
for {
206-
err := <-errChan
207-
if err != nil {
208-
// Log only in debug mode
209-
// Connection errors to the forwarded port should not exit the process
210-
log.Debug(err)
211-
}
212-
}
213-
}
214-
215-
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"
216-
217-
func checkAndAlertVSCodeInstalled() {
218-
if err := isVSCodeInstalled(); err != nil {
219-
redBold := "\033[1;31m" // ANSI escape code for red and bold
220-
reset := "\033[0m" // ANSI escape code to reset text formatting
221-
222-
errorMessage := "Please install Visual Studio Code and ensure it's in your PATH. "
223-
infoMessage := "More information on: 'https://code.visualstudio.com/docs/editor/command-line#_launching-from-command-line'"
224-
225-
log.Error(redBold + errorMessage + reset + infoMessage)
226-
227-
return
228-
}
229-
}
230-
231-
func isVSCodeInstalled() error {
232-
_, err := exec.LookPath("code")
233-
return err
134+
return errors.New("invalid IDE")
234135
}
235136

236137
var ideFlag string

0 commit comments

Comments
 (0)