1package setup
2
3import (
4 "context"
5 "fmt"
6 "io"
7 "os/exec"
8 "strings"
9
10 "github.com/opencode-ai/opencode/internal/config"
11 "github.com/opencode-ai/opencode/internal/lsp/protocol"
12)
13
14// InstallationResult represents the result of an LSP server installation
15type InstallationResult struct {
16 ServerName string
17 Success bool
18 Error error
19 Output string
20}
21
22// InstallLSPServer installs an LSP server for the given language
23func InstallLSPServer(ctx context.Context, server LSPServerInfo) InstallationResult {
24 result := InstallationResult{
25 ServerName: server.Name,
26 Success: false,
27 }
28
29 // Check if the server is already installed
30 if _, err := exec.LookPath(server.Command); err == nil {
31 result.Success = true
32 result.Output = fmt.Sprintf("%s is already installed", server.Name)
33 return result
34 }
35
36 // Parse the installation command
37 installCmd, installArgs := parseInstallCommand(server.InstallCmd)
38
39 // If the installation command is a URL or instructions, return with error
40 if strings.HasPrefix(installCmd, "http") || strings.Contains(installCmd, "Manual installation") {
41 result.Error = fmt.Errorf("manual installation required: %s", server.InstallCmd)
42 result.Output = server.InstallCmd
43 return result
44 }
45
46 // Execute the installation command
47 cmd := exec.CommandContext(ctx, installCmd, installArgs...)
48
49 // Set up pipes for stdout and stderr
50 stdout, err := cmd.StdoutPipe()
51 if err != nil {
52 result.Error = fmt.Errorf("failed to create stdout pipe: %w", err)
53 return result
54 }
55
56 stderr, err := cmd.StderrPipe()
57 if err != nil {
58 result.Error = fmt.Errorf("failed to create stderr pipe: %w", err)
59 return result
60 }
61
62 // Start the command
63 if err := cmd.Start(); err != nil {
64 result.Error = fmt.Errorf("failed to start installation: %w", err)
65 return result
66 }
67
68 // Read output
69 stdoutBytes, _ := io.ReadAll(stdout)
70 stderrBytes, _ := io.ReadAll(stderr)
71
72 // Wait for the command to finish
73 if err := cmd.Wait(); err != nil {
74 result.Error = fmt.Errorf("installation failed: %w", err)
75 result.Output = fmt.Sprintf("stdout: %s\nstderr: %s", string(stdoutBytes), string(stderrBytes))
76 return result
77 }
78
79 // Check if the server is now installed
80 if _, err := exec.LookPath(server.Command); err != nil {
81 result.Error = fmt.Errorf("installation completed but server not found in PATH")
82 result.Output = fmt.Sprintf("stdout: %s\nstderr: %s", string(stdoutBytes), string(stderrBytes))
83 return result
84 }
85
86 result.Success = true
87 result.Output = fmt.Sprintf("Successfully installed %s\nstdout: %s\nstderr: %s",
88 server.Name, string(stdoutBytes), string(stderrBytes))
89
90 return result
91}
92
93// parseInstallCommand parses an installation command string into command and arguments
94func parseInstallCommand(installCmd string) (string, []string) {
95 parts := strings.Fields(installCmd)
96 if len(parts) == 0 {
97 return "", nil
98 }
99
100 return parts[0], parts[1:]
101}
102
103// GetInstallationCommands returns the installation commands for the given servers
104func GetInstallationCommands(servers LSPServerMap) map[string]string {
105 commands := make(map[string]string)
106
107 for _, serverList := range servers {
108 for _, server := range serverList {
109 if server.Recommended {
110 commands[server.Name] = server.InstallCmd
111 }
112 }
113 }
114
115 return commands
116}
117
118// VerifyInstallation verifies that an LSP server is correctly installed
119func VerifyInstallation(serverName string) bool {
120 _, err := exec.LookPath(serverName)
121 return err == nil
122}
123
124// UpdateLSPConfig updates the LSP configuration in the config file
125func UpdateLSPConfig(servers map[protocol.LanguageKind]LSPServerInfo) error {
126 // Create a map for the LSP configuration
127 lspConfig := make(map[string]config.LSPConfig)
128
129 for lang, server := range servers {
130 langStr := string(lang)
131
132 lspConfig[langStr] = config.LSPConfig{
133 Disabled: false,
134 Command: server.Command,
135 Args: server.Args,
136 Options: server.Options,
137 }
138 }
139
140 return config.SaveLocalLSPConfig(lspConfig)
141}