All checks were successful
CI / build (push) Successful in 2m41s
Split monolithic app.go into focused modules (dashboard, chat, workflow, config, agents, terminal, commands, handlers). Add proper error handling for installer commands, proxy pipes, and MCP config parsing. Fix daemon channel buffer, cap orchestrator history, compile think regex once, and set HTTP timeouts on preview server. Improve CI with Go module caching, dependency download step, and test stage with race detection. 😘 Generated with Crush Assisted-by: GLM-5-Turbo via Crush <crush@charm.land>
196 lines
6.9 KiB
Go
196 lines
6.9 KiB
Go
package lsp
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
|
|
"github.com/muyue/muyue/internal/config"
|
|
)
|
|
|
|
type LSPServer struct {
|
|
Name string `json:"name"`
|
|
Language string `json:"language"`
|
|
Command string `json:"command"`
|
|
InstallCmd string `json:"install_cmd"`
|
|
ConfigFile string `json:"config_file"`
|
|
Installed bool `json:"installed"`
|
|
}
|
|
|
|
type LSPConfig struct {
|
|
Servers []LSPServer `json:"servers"`
|
|
}
|
|
|
|
var knownServers = []LSPServer{
|
|
{Name: "gopls", Language: "go", Command: "gopls", InstallCmd: "go install golang.org/x/tools/gopls@latest"},
|
|
{Name: "pyright", Language: "python", Command: "pyright", InstallCmd: "npm install -g pyright"},
|
|
{Name: "typescript-language-server", Language: "typescript", Command: "typescript-language-server", InstallCmd: "npm install -g typescript-language-server typescript"},
|
|
{Name: "vscode-json-language-server", Language: "json", Command: "vscode-json-language-server", InstallCmd: "npm install -g vscode-langservers-extracted"},
|
|
{Name: "vscode-html-language-server", Language: "html", Command: "vscode-html-language-server", InstallCmd: "npm install -g vscode-langservers-extracted"},
|
|
{Name: "vscode-css-language-server", Language: "css", Command: "vscode-css-language-server", InstallCmd: "npm install -g vscode-langservers-extracted"},
|
|
{Name: "yaml-language-server", Language: "yaml", Command: "yaml-language-server", InstallCmd: "npm install -g yaml-language-server"},
|
|
{Name: "bash-language-server", Language: "bash", Command: "bash-language-server", InstallCmd: "npm install -g bash-language-server"},
|
|
{Name: "rust-analyzer", Language: "rust", Command: "rust-analyzer", InstallCmd: "rustup component add rust-analyzer"},
|
|
{Name: "clangd", Language: "c/c++", Command: "clangd", InstallCmd: ""},
|
|
{Name: "lua-language-server", Language: "lua", Command: "lua-language-server", InstallCmd: "npm install -g lua-language-server"},
|
|
{Name: "dockerfile-language-server", Language: "dockerfile", Command: "docker-langserver", InstallCmd: "npm install -g dockerfile-language-server-nodejs"},
|
|
{Name: "tailwindcss-language-server", Language: "tailwind", Command: "tailwindcss-language-server", InstallCmd: "npm install -g @tailwindcss/language-server"},
|
|
{Name: "svelte-language-server", Language: "svelte", Command: "svelteserver", InstallCmd: "npm install -g svelte-language-server"},
|
|
{Name: "vue-language-server", Language: "vue", Command: "vue-language-server", InstallCmd: "npm install -g @vue/language-server"},
|
|
{Name: "golangci-lint-langserver", Language: "go-lint", Command: "golangci-lint-langserver", InstallCmd: "go install github.com/nametake/golangci-lint-langserver@latest"},
|
|
}
|
|
|
|
func ScanServers() []LSPServer {
|
|
servers := make([]LSPServer, len(knownServers))
|
|
for i, s := range knownServers {
|
|
servers[i] = s
|
|
_, err := exec.LookPath(s.Command)
|
|
servers[i].Installed = err == nil
|
|
}
|
|
return servers
|
|
}
|
|
|
|
func InstallServer(name string) error {
|
|
for _, s := range knownServers {
|
|
if s.Name == name {
|
|
if s.InstallCmd == "" {
|
|
return fmt.Errorf("%s has no auto-install command, install manually", name)
|
|
}
|
|
cmd := exec.Command("bash", "-c", s.InstallCmd)
|
|
cmd.Env = os.Environ()
|
|
if output, err := cmd.CombinedOutput(); err != nil {
|
|
return fmt.Errorf("install %s: %s: %w", name, string(output), err)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
return fmt.Errorf("unknown LSP server: %s", name)
|
|
}
|
|
|
|
func InstallForLanguages(languages []string) []LSPServer {
|
|
langMap := map[string][]string{
|
|
"go": {"gopls"},
|
|
"typescript": {"typescript-language-server", "vscode-json-language-server", "vscode-html-language-server", "vscode-css-language-server"},
|
|
"javascript": {"typescript-language-server", "vscode-json-language-server", "vscode-html-language-server", "vscode-css-language-server"},
|
|
"python": {"pyright"},
|
|
"rust": {"rust-analyzer"},
|
|
"c": {"clangd"},
|
|
"cpp": {"clangd"},
|
|
"json": {"vscode-json-language-server"},
|
|
"yaml": {"yaml-language-server"},
|
|
"bash": {"bash-language-server"},
|
|
"html": {"vscode-html-language-server"},
|
|
"css": {"vscode-css-language-server"},
|
|
"lua": {"lua-language-server"},
|
|
"docker": {"dockerfile-language-server"},
|
|
"svelte": {"svelte-language-server"},
|
|
"vue": {"vue-language-server"},
|
|
}
|
|
|
|
installed := map[string]bool{}
|
|
var results []LSPServer
|
|
|
|
for _, lang := range languages {
|
|
if servers, ok := langMap[lang]; ok {
|
|
for _, srv := range servers {
|
|
if installed[srv] {
|
|
continue
|
|
}
|
|
installed[srv] = true
|
|
if err := InstallServer(srv); err != nil {
|
|
results = append(results, LSPServer{Name: srv, Language: lang, Installed: false})
|
|
} else {
|
|
results = append(results, LSPServer{Name: srv, Language: lang, Installed: true})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return results
|
|
}
|
|
|
|
func GenerateCrushConfig(cfg *config.MuyueConfig) error {
|
|
if cfg == nil {
|
|
return fmt.Errorf("config is nil")
|
|
}
|
|
|
|
configDir, err := config.ConfigDir()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
type lspEntry struct {
|
|
Command []string `json:"command"`
|
|
}
|
|
|
|
lspConfig := map[string]lspEntry{}
|
|
|
|
for _, lang := range cfg.Profile.Languages {
|
|
switch lang {
|
|
case "go":
|
|
lspConfig["go"] = lspEntry{Command: []string{"gopls"}}
|
|
case "python":
|
|
lspConfig["python"] = lspEntry{Command: []string{"pyright-langserver", "--stdio"}}
|
|
case "typescript", "javascript":
|
|
lspConfig["typescript"] = lspEntry{Command: []string{"typescript-language-server", "--stdio"}}
|
|
case "rust":
|
|
lspConfig["rust"] = lspEntry{Command: []string{"rust-analyzer"}}
|
|
case "c", "cpp":
|
|
lspConfig["c"] = lspEntry{Command: []string{"clangd"}}
|
|
case "lua":
|
|
lspConfig["lua"] = lspEntry{Command: []string{"lua-language-server"}}
|
|
}
|
|
}
|
|
|
|
if len(lspConfig) == 0 {
|
|
return nil
|
|
}
|
|
|
|
data, err := json.MarshalIndent(lspConfig, "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
lspPath := filepath.Join(configDir, "crush.json")
|
|
existing, err := os.ReadFile(lspPath)
|
|
if err == nil {
|
|
var existingConfig map[string]interface{}
|
|
if unmarshalErr := json.Unmarshal(existing, &existingConfig); unmarshalErr == nil {
|
|
var newConfig map[string]interface{}
|
|
if unmarshalErr2 := json.Unmarshal(data, &newConfig); unmarshalErr2 == nil {
|
|
for k, v := range newConfig {
|
|
existingConfig[k] = v
|
|
}
|
|
data, _ = json.MarshalIndent(existingConfig, "", " ")
|
|
}
|
|
}
|
|
}
|
|
|
|
return os.WriteFile(lspPath, data, 0644)
|
|
}
|
|
|
|
func EnsureCrushConfig(cfg *config.MuyueConfig) error {
|
|
configDir, _ := config.ConfigDir()
|
|
crusherPath := filepath.Join(configDir, "crush.json")
|
|
|
|
if _, err := os.Stat(crusherPath); err != nil {
|
|
home, _ := os.UserHomeDir()
|
|
homeCrush := filepath.Join(home, ".config", "crush", "crush.json")
|
|
if _, err := os.Stat(homeCrush); err == nil {
|
|
return nil
|
|
}
|
|
|
|
defaultConfig := map[string]interface{}{
|
|
"version": "1",
|
|
}
|
|
|
|
data, _ := json.MarshalIndent(defaultConfig, "", " ")
|
|
os.MkdirAll(filepath.Dir(crusherPath), 0755)
|
|
return os.WriteFile(crusherPath, data, 0644)
|
|
}
|
|
|
|
return nil
|
|
}
|