Detailed changes
@@ -35,6 +35,9 @@ type App struct {
LSPClients *csync.Map[string, *lsp.Client]
+ lspStates *csync.Map[string, LSPClientInfo]
+ lspBroker *pubsub.Broker[LSPEvent]
+
config *config.Config
serviceEventsWG *sync.WaitGroup
@@ -65,6 +68,8 @@ func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
History: files,
Permissions: permission.NewPermissionService(cfg.WorkingDir(), skipPermissionsRequests, allowedTools),
LSPClients: csync.NewMap[string, *lsp.Client](),
+ lspStates: csync.NewMap[string, LSPClientInfo](),
+ lspBroker: pubsub.NewBroker[LSPEvent](),
globalCtx: ctx,
@@ -220,7 +225,7 @@ func (app *App) setupEvents() {
setupSubscriber(ctx, app.serviceEventsWG, "permissions-notifications", app.Permissions.SubscribeNotifications, app.events)
setupSubscriber(ctx, app.serviceEventsWG, "history", app.History.Subscribe, app.events)
setupSubscriber(ctx, app.serviceEventsWG, "mcp", agent.SubscribeMCPEvents, app.events)
- setupSubscriber(ctx, app.serviceEventsWG, "lsp", SubscribeLSPEvents, app.events)
+ setupSubscriber(ctx, app.serviceEventsWG, "lsp", app.SubscribeLSPEvents, app.events)
cleanupFunc := func() error {
cancel()
app.serviceEventsWG.Wait()
@@ -28,23 +28,23 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, config
// Check if any root markers exist in the working directory (config now has defaults)
if !lsp.HasRootMarkers(app.config.WorkingDir(), config.RootMarkers) {
slog.Info("Skipping LSP client - no root markers found", "name", name, "rootMarkers", config.RootMarkers)
- updateLSPState(name, lsp.StateDisabled, nil, nil, 0)
+ app.updateLSPState(name, lsp.StateDisabled, nil, 0)
return
}
// Update state to starting
- updateLSPState(name, lsp.StateStarting, nil, nil, 0)
+ app.updateLSPState(name, lsp.StateStarting, nil, 0)
// Create LSP client.
lspClient, err := lsp.New(ctx, name, config)
if err != nil {
slog.Error("Failed to create LSP client for", name, err)
- updateLSPState(name, lsp.StateError, err, nil, 0)
+ app.updateLSPState(name, lsp.StateError, err, 0)
return
}
// Set diagnostics callback
- lspClient.SetDiagnosticsCallback(updateLSPDiagnostics)
+ lspClient.SetDiagnosticsCallback(app.updateLSPDiagnostics)
// Increase initialization timeout as some servers take more time to start.
initCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
@@ -54,7 +54,7 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, config
_, err = lspClient.Initialize(initCtx, app.config.WorkingDir())
if err != nil {
slog.Error("Initialize failed", "name", name, "error", err)
- updateLSPState(name, lsp.StateError, err, lspClient, 0)
+ app.updateLSPState(name, lsp.StateError, err, 0)
lspClient.Close(ctx)
return
}
@@ -65,12 +65,12 @@ func (app *App) createAndStartLSPClient(ctx context.Context, name string, config
// Server never reached a ready state, but let's continue anyway, as
// some functionality might still work.
lspClient.SetServerState(lsp.StateError)
- updateLSPState(name, lsp.StateError, err, lspClient, 0)
+ app.updateLSPState(name, lsp.StateError, err, 0)
} else {
// Server reached a ready state scuccessfully.
slog.Info("LSP server is ready", "name", name)
lspClient.SetServerState(lsp.StateReady)
- updateLSPState(name, lsp.StateReady, nil, lspClient, 0)
+ app.updateLSPState(name, lsp.StateReady, nil, 0)
}
slog.Info("LSP client initialized", "name", name)
@@ -5,7 +5,6 @@ import (
"maps"
"time"
- "github.com/charmbracelet/crush/internal/csync"
"github.com/charmbracelet/crush/internal/lsp"
"github.com/charmbracelet/crush/internal/pubsub"
)
@@ -38,50 +37,43 @@ type LSPEvent struct {
// LSPClientInfo holds information about an LSP client's state
type LSPClientInfo struct {
- Name string
- State lsp.ServerState
- Error error
- Client *lsp.Client
- DiagnosticCount int
- ConnectedAt time.Time
+ Name string `json:"name"`
+ State lsp.ServerState `json:"state"`
+ Error error `json:"error,omitempty"`
+ DiagnosticCount int `json:"diagnostic_count,omitempty"`
+ ConnectedAt time.Time `json:"connected_at"`
}
-var (
- lspStates = csync.NewMap[string, LSPClientInfo]()
- lspBroker = pubsub.NewBroker[LSPEvent]()
-)
-
// SubscribeLSPEvents returns a channel for LSP events
-func SubscribeLSPEvents(ctx context.Context) <-chan pubsub.Event[LSPEvent] {
- return lspBroker.Subscribe(ctx)
+func (a *App) SubscribeLSPEvents(ctx context.Context) <-chan pubsub.Event[LSPEvent] {
+ return a.lspBroker.Subscribe(ctx)
}
// GetLSPStates returns the current state of all LSP clients
-func GetLSPStates() map[string]LSPClientInfo {
- return maps.Collect(lspStates.Seq2())
+func (a *App) GetLSPStates() map[string]LSPClientInfo {
+ return maps.Collect(a.lspStates.Seq2())
}
// GetLSPState returns the state of a specific LSP client
-func GetLSPState(name string) (LSPClientInfo, bool) {
- return lspStates.Get(name)
+func (a *App) GetLSPState(name string) (LSPClientInfo, bool) {
+ return a.lspStates.Get(name)
}
// updateLSPState updates the state of an LSP client and publishes an event
-func updateLSPState(name string, state lsp.ServerState, err error, client *lsp.Client, diagnosticCount int) {
+func (a *App) updateLSPState(name string, state lsp.ServerState, err error, diagnosticCount int) {
info := LSPClientInfo{
Name: name,
State: state,
Error: err,
- Client: client,
DiagnosticCount: diagnosticCount,
}
if state == lsp.StateReady {
info.ConnectedAt = time.Now()
}
- lspStates.Set(name, info)
+ a.lspStates.Set(name, info)
// Publish state change event
- lspBroker.Publish(pubsub.UpdatedEvent, LSPEvent{
+ a.lspBroker.Publish(pubsub.UpdatedEvent, LSPEvent{
Type: LSPEventStateChanged,
Name: name,
State: state,
@@ -91,13 +83,13 @@ func updateLSPState(name string, state lsp.ServerState, err error, client *lsp.C
}
// updateLSPDiagnostics updates the diagnostic count for an LSP client and publishes an event
-func updateLSPDiagnostics(name string, diagnosticCount int) {
- if info, exists := lspStates.Get(name); exists {
+func (a *App) updateLSPDiagnostics(name string, diagnosticCount int) {
+ if info, exists := a.lspStates.Get(name); exists {
info.DiagnosticCount = diagnosticCount
- lspStates.Set(name, info)
+ a.lspStates.Set(name, info)
// Publish diagnostics change event
- lspBroker.Publish(pubsub.UpdatedEvent, LSPEvent{
+ a.lspBroker.Publish(pubsub.UpdatedEvent, LSPEvent{
Type: LSPEventDiagnosticsChanged,
Name: name,
State: info.State,
@@ -1,8 +1,47 @@
package message
+import (
+ "encoding/base64"
+ "encoding/json"
+)
+
type Attachment struct {
- FilePath string
- FileName string
- MimeType string
- Content []byte
+ FilePath string `json:"file_path"`
+ FileName string `json:"file_name"`
+ MimeType string `json:"mime_type"`
+ Content []byte `json:"content"`
+}
+
+// MarshalJSON implements the [json.Marshaler] interface.
+func (a Attachment) MarshalJSON() ([]byte, error) {
+ // Encode the content as a base64 string
+ type Alias Attachment
+ return json.Marshal(&struct {
+ Content string `json:"content"`
+ *Alias
+ }{
+ Content: base64.StdEncoding.EncodeToString(a.Content),
+ Alias: (*Alias)(&a),
+ })
+}
+
+// UnmarshalJSON implements the [json.Unmarshaler] interface.
+func (a *Attachment) UnmarshalJSON(data []byte) error {
+ // Decode the content from a base64 string
+ type Alias Attachment
+ aux := &struct {
+ Content string `json:"content"`
+ *Alias
+ }{
+ Alias: (*Alias)(a),
+ }
+ if err := json.Unmarshal(data, &aux); err != nil {
+ return err
+ }
+ content, err := base64.StdEncoding.DecodeString(aux.Content)
+ if err != nil {
+ return err
+ }
+ a.Content = content
+ return nil
}