package main import ( "fmt" "io" "net/http" "os" "os/exec" "path/filepath" "strings" // "github.com/adrg/xdg" ) // Installer verwaltet die Installation von LSP-Servern type Installer struct { installDir string binDir string } // NewInstaller erstellt einen XDG-konformen Installer func NewInstaller(baseDir string) *Installer { installDir := filepath.Join(baseDir, "servers") binDir := filepath.Join(baseDir, "bin") // Verzeichnisse erstellen os.MkdirAll(installDir, 0755) os.MkdirAll(binDir, 0755) return &Installer{ installDir: installDir, binDir: binDir, } } // PrintPathInstructions zeigt dem Benutzer, wie der PATH gesetzt wird func (i *Installer) PrintPathInstructions() { fmt.Println("\n📁 LSP-Server werden installiert in:") fmt.Printf(" Binaries: %s\n", i.binDir) fmt.Printf(" Data: %s\n\n", i.installDir) // Prüfen ob bereits im PATH pathEnv := os.Getenv("PATH") pathList := filepath.SplitList(pathEnv) inPath := false for _, p := range pathList { if p == i.binDir { inPath = true break } } if !inPath { fmt.Println("⚠️ Das Binary-Verzeichnis ist noch nicht im PATH!") fmt.Println("\nFüge folgende Zeile zu deiner Shell-Konfiguration hinzu:") fmt.Printf("\n # Für Bash (~/.bashrc):\n") fmt.Printf(" export PATH=\"%s:$PATH\"\n\n", i.binDir) fmt.Printf(" # Für Zsh (~/.zshrc):\n") fmt.Printf(" export PATH=\"%s:$PATH\"\n\n", i.binDir) fmt.Printf(" # Für Fish (~/.config/fish/config.fish):\n") fmt.Printf(" set -x PATH %s $PATH\n\n", i.binDir) } else { fmt.Println("✅ Binary-Verzeichnis ist bereits im PATH!") } } // Install installiert einen LSP-Server basierend auf dem Typ func (i *Installer) Install(server LSPServer) error { switch server.InstallType { case "go", "golang": return i.installGo(server) case "npm": return i.installNpm(server) case "github": return i.installGithub(server) case "cargo": return i.installCargo(server) default: return fmt.Errorf("unsupported install type: %s (für %s)", server.InstallType, server.Name) } } // installGo installiert einen LSP-Server via go install func (i *Installer) installGo(server LSPServer) error { // Mason Source format: "golang.org/x/tools/gopls" oder ähnlich source := server.Source if !strings.Contains(source, "@") { source = source + "@" + server.Version } cmd := exec.Command("go", "install", source) cmd.Env = append(os.Environ(), "GOBIN="+i.binDir, ) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("go install failed: %v\nOutput: %s", err, output) } return nil } // installNpm installiert einen LSP-Server via npm func (i *Installer) installNpm(server LSPServer) error { serverDir := filepath.Join(i.installDir, server.Name) os.MkdirAll(serverDir, 0755) // Mason Source format: "package-name" (einfacher npm Package-Name) cmd := exec.Command("npm", "install", "-g", "--prefix", serverDir, server.Source) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("npm install failed: %v\nOutput: %s", err, output) } // Symlink zum bin-Verzeichnis erstellen // npm installiert in serverDir/lib/node_modules/.bin/ binPath := filepath.Join(serverDir, "lib", "node_modules", ".bin", server.Executable) linkPath := filepath.Join(i.binDir, server.Executable) os.Remove(linkPath) // Alten Link entfernen return os.Symlink(binPath, linkPath) } // installGithub lädt ein Binary von GitHub Releases herunter func (i *Installer) installGithub(server LSPServer) error { // Mason Source format: "owner/repo" // Wir müssen die Download-URL konstruieren (vereinfacht) downloadURL := fmt.Sprintf("https://github.com/%s/releases/latest/download/%s", server.Source, server.Executable) resp, err := http.Get(downloadURL) if err != nil { return fmt.Errorf("download failed: %w", err) } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { return fmt.Errorf("download failed with status: %d", resp.StatusCode) } targetPath := filepath.Join(i.binDir, server.Executable) file, err := os.Create(targetPath) if err != nil { return err } defer file.Close() _, err = io.Copy(file, resp.Body) if err != nil { return err } // Executable-Rechte setzen return os.Chmod(targetPath, 0755) } // installCargo installiert einen LSP-Server via cargo func (i *Installer) installCargo(server LSPServer) error { serverDir := filepath.Join(i.installDir, server.Name) // Mason Source format: "crate-name" cmd := exec.Command("cargo", "install", server.Source, "--root", serverDir) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("cargo install failed: %v\nOutput: %s", err, output) } // Symlink erstellen binPath := filepath.Join(serverDir, "bin", server.Executable) linkPath := filepath.Join(i.binDir, server.Executable) os.Remove(linkPath) return os.Symlink(binPath, linkPath) } // IsInstalled prüft, ob ein Server installiert ist func (i *Installer) IsInstalled(server LSPServer) bool { binPath := filepath.Join(i.binDir, server.Executable) _, err := os.Stat(binPath) return err == nil } // Uninstall entfernt einen installierten LSP-Server func (i *Installer) Uninstall(server LSPServer) error { binPath := filepath.Join(i.binDir, server.Executable) serverDir := filepath.Join(i.installDir, server.Name) os.Remove(binPath) os.RemoveAll(serverDir) return nil }