install.go

  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}