1package tools
2
3import (
4 "context"
5 _ "embed"
6 "fmt"
7 "log/slog"
8 "maps"
9 "strings"
10 "sync"
11
12 "charm.land/fantasy"
13 "github.com/charmbracelet/crush/internal/lsp"
14)
15
16const LSPRestartToolName = "lsp_restart"
17
18//go:embed lsp_restart.md
19var lspRestartDescription []byte
20
21type LSPRestartParams struct {
22 // Name is the optional name of a specific LSP client to restart.
23 // If empty, all LSP clients will be restarted.
24 Name string `json:"name,omitempty"`
25}
26
27func NewLSPRestartTool(lspManager *lsp.Manager) fantasy.AgentTool {
28 return fantasy.NewAgentTool(
29 LSPRestartToolName,
30 string(lspRestartDescription),
31 func(ctx context.Context, params LSPRestartParams, call fantasy.ToolCall) (fantasy.ToolResponse, error) {
32 if lspManager.Clients().Len() == 0 {
33 return fantasy.NewTextErrorResponse("no LSP clients available to restart"), nil
34 }
35
36 clientsToRestart := make(map[string]*lsp.Client)
37 if params.Name == "" {
38 maps.Insert(clientsToRestart, lspManager.Clients().Seq2())
39 } else {
40 client, exists := lspManager.Clients().Get(params.Name)
41 if !exists {
42 return fantasy.NewTextErrorResponse(fmt.Sprintf("LSP client '%s' not found", params.Name)), nil
43 }
44 clientsToRestart[params.Name] = client
45 }
46
47 var restarted []string
48 var failed []string
49 var mu sync.Mutex
50 var wg sync.WaitGroup
51 for name, client := range clientsToRestart {
52 wg.Go(func() {
53 if err := client.Restart(); err != nil {
54 slog.Error("Failed to restart LSP client", "name", name, "error", err)
55 mu.Lock()
56 failed = append(failed, name)
57 mu.Unlock()
58 return
59 }
60 mu.Lock()
61 restarted = append(restarted, name)
62 mu.Unlock()
63 })
64 }
65
66 wg.Wait()
67
68 var output string
69 if len(restarted) > 0 {
70 output = fmt.Sprintf("Successfully restarted %d LSP client(s): %s\n", len(restarted), strings.Join(restarted, ", "))
71 }
72 if len(failed) > 0 {
73 output += fmt.Sprintf("Failed to restart %d LSP client(s): %s\n", len(failed), strings.Join(failed, ", "))
74 return fantasy.NewTextErrorResponse(output), nil
75 }
76
77 return fantasy.NewTextResponse(output), nil
78 })
79}