on main help sign in
chore: tidy various comments
fe9fdf2Christian Rocha
internal/app/app.go+18−7
@@ -53,6 +53,7 @@ type App struct {
53 cleanupFuncs []func()
53 cleanupFuncs []func()
54}
54}
55
55
56+// New initializes a new applcation instance.
56func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
57func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
57 q := db.New(conn)
58 q := db.New(conn)
58 sessions := session.NewService(q)
59 sessions := session.NewService(q)
@@ -78,10 +79,10 @@ func New(ctx context.Context, conn *sql.DB, cfg *config.Config) (*App, error) {
78
79
79 app.setupEvents()
80 app.setupEvents()
80
81
81− // Initialize LSP clients in the background
82+ // Initialize LSP clients in the background.
82 go app.initLSPClients(ctx)
83 go app.initLSPClients(ctx)
83
84
84− // TODO: remove the concept of agent config most likely
85+ // TODO: remove the concept of agent config, most likely.
85 if cfg.IsConfigured() {
86 if cfg.IsConfigured() {
86 if err := app.InitCoderAgent(); err != nil {
87 if err := app.InitCoderAgent(); err != nil {
87 return nil, fmt.Errorf("failed to initialize coder agent: %w", err)
88 return nil, fmt.Errorf("failed to initialize coder agent: %w", err)
@@ -97,20 +98,22 @@ func (app *App) Config() *config.Config {
97 return app.config
98 return app.config
98}
99}
99
100
100−// RunNonInteractive handles the execution flow when a prompt is provided via CLI flag.
101+// RunNonInteractive handles the execution flow when a prompt is provided via
102+// CLI flag.
101func (app *App) RunNonInteractive(ctx context.Context, prompt string, quiet bool) error {
103func (app *App) RunNonInteractive(ctx context.Context, prompt string, quiet bool) error {
102 slog.Info("Running in non-interactive mode")
104 slog.Info("Running in non-interactive mode")
103
105
104 ctx, cancel := context.WithCancel(ctx)
106 ctx, cancel := context.WithCancel(ctx)
105 defer cancel()
107 defer cancel()
106
108
107− // Start spinner if not in quiet mode
109+ // Start spinner if not in quiet mode.
108 var spinner *format.Spinner
110 var spinner *format.Spinner
109 if !quiet {
111 if !quiet {
110 spinner = format.NewSpinner(ctx, cancel, "Generating")
112 spinner = format.NewSpinner(ctx, cancel, "Generating")
111 spinner.Start()
113 spinner.Start()
112 }
114 }
115+
113− // Helper function to stop spinner once
116+ // Helper function to stop spinner once.
114 stopSpinner := func() {
117 stopSpinner := func() {
115 if !quiet && spinner != nil {
118 if !quiet && spinner != nil {
116 spinner.Stop()
119 spinner.Stop()
@@ -257,9 +260,10 @@ func (app *App) InitCoderAgent() error {
257 return nil
260 return nil
258}
261}
259
262
263+// Subscribe sends events to the TUI as tea.Msgs.
260func (app *App) Subscribe(program *tea.Program) {
264func (app *App) Subscribe(program *tea.Program) {
261 defer log.RecoverPanic("app.Subscribe", func() {
265 defer log.RecoverPanic("app.Subscribe", func() {
262− slog.Info("TUI subscription panic - attempting graceful shutdown")
266+ slog.Info("TUI subscription panic: attempting graceful shutdown")
263 program.Quit()
267 program.Quit()
264 })
268 })
265
269
@@ -271,6 +275,7 @@ func (app *App) Subscribe(program *tea.Program) {
271 app.tuiWG.Wait()
275 app.tuiWG.Wait()
272 })
276 })
273 defer app.tuiWG.Done()
277 defer app.tuiWG.Done()
278+
274 for {
279 for {
275 select {
280 select {
276 case <-tuiCtx.Done():
281 case <-tuiCtx.Done():
@@ -286,23 +291,28 @@ func (app *App) Subscribe(program *tea.Program) {
286 }
291 }
287}
292}
288
293
289−// Shutdown performs a clean shutdown of the application
294+// Shutdown performs a graceful shutdown of the application.
290func (app *App) Shutdown() {
295func (app *App) Shutdown() {
291 if app.CoderAgent != nil {
296 if app.CoderAgent != nil {
292 app.CoderAgent.CancelAll()
297 app.CoderAgent.CancelAll()
293 }
298 }
299+
294 app.cancelFuncsMutex.Lock()
300 app.cancelFuncsMutex.Lock()
295 for _, cancel := range app.watcherCancelFuncs {
301 for _, cancel := range app.watcherCancelFuncs {
296 cancel()
302 cancel()
297 }
303 }
298 app.cancelFuncsMutex.Unlock()
304 app.cancelFuncsMutex.Unlock()
305+
306+ // Wait for all LSP watchers to finish.
299 app.lspWatcherWG.Wait()
307 app.lspWatcherWG.Wait()
300
308
309+ // Get all LSP clients.
301 app.clientsMutex.RLock()
310 app.clientsMutex.RLock()
302 clients := make(map[string]*lsp.Client, len(app.LSPClients))
311 clients := make(map[string]*lsp.Client, len(app.LSPClients))
303 maps.Copy(clients, app.LSPClients)
312 maps.Copy(clients, app.LSPClients)
304 app.clientsMutex.RUnlock()
313 app.clientsMutex.RUnlock()
305
314
315+ // Shutdown all LSP clients.
306 for name, client := range clients {
316 for name, client := range clients {
307 shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)
317 shutdownCtx, cancel := context.WithTimeout(app.globalCtx, 5*time.Second)
308 if err := client.Shutdown(shutdownCtx); err != nil {
318 if err := client.Shutdown(shutdownCtx); err != nil {
@@ -311,6 +321,7 @@ func (app *App) Shutdown() {
311 cancel()
321 cancel()
312 }
322 }
313
323
324+ // Call call cleanup functions.
314 for _, cleanup := range app.cleanupFuncs {
325 for _, cleanup := range app.cleanupFuncs {
315 if cleanup != nil {
326 if cleanup != nil {
316 cleanup()
327 cleanup()
internal/app/lsp.go+23−23
@@ -10,10 +10,9 @@ import (
10 "github.com/charmbracelet/crush/internal/lsp/watcher"
10 "github.com/charmbracelet/crush/internal/lsp/watcher"
11)
11)
12
12
13+// initLSPClients initializes LSP clients.
13func (app *App) initLSPClients(ctx context.Context) {
14func (app *App) initLSPClients(ctx context.Context) {
14− // Initialize LSP clients
15 for name, clientConfig := range app.config.LSP {
15 for name, clientConfig := range app.config.LSP {
16− // Start each client initialization in its own goroutine
17 go app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
16 go app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
18 }
17 }
19 slog.Info("LSP clients initialization started in background")
18 slog.Info("LSP clients initialization started in background")
@@ -21,68 +20,68 @@ func (app *App) initLSPClients(ctx context.Context) {
21
20
22// createAndStartLSPClient creates a new LSP client, initializes it, and starts its workspace watcher
21// createAndStartLSPClient creates a new LSP client, initializes it, and starts its workspace watcher
23func (app *App) createAndStartLSPClient(ctx context.Context, name string, command string, args ...string) {
22func (app *App) createAndStartLSPClient(ctx context.Context, name string, command string, args ...string) {
24− // Create a specific context for initialization with a timeout
25 slog.Info("Creating LSP client", "name", name, "command", command, "args", args)
23 slog.Info("Creating LSP client", "name", name, "command", command, "args", args)
26
24
27− // Create the LSP client
25+ // Create LSP client.
28 lspClient, err := lsp.NewClient(ctx, command, args...)
26 lspClient, err := lsp.NewClient(ctx, command, args...)
29 if err != nil {
27 if err != nil {
30 slog.Error("Failed to create LSP client for", name, err)
28 slog.Error("Failed to create LSP client for", name, err)
31 return
29 return
32 }
30 }
33
31
34− // Create a longer timeout for initialization (some servers take time to start)
32+ // Increase initialization timeout as some servers take more time to start.
35 initCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
33 initCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
36 defer cancel()
34 defer cancel()
37
35
38− // Initialize with the initialization context
36+ // Initialize LSP client.
39 _, err = lspClient.InitializeLSPClient(initCtx, app.config.WorkingDir())
37 _, err = lspClient.InitializeLSPClient(initCtx, app.config.WorkingDir())
40 if err != nil {
38 if err != nil {
41 slog.Error("Initialize failed", "name", name, "error", err)
39 slog.Error("Initialize failed", "name", name, "error", err)
42− // Clean up the client to prevent resource leaks
43 lspClient.Close()
40 lspClient.Close()
44 return
41 return
45 }
42 }
46
43
47− // Wait for the server to be ready
44+ // Wait for the server to be ready.
48 if err := lspClient.WaitForServerReady(initCtx); err != nil {
45 if err := lspClient.WaitForServerReady(initCtx); err != nil {
49 slog.Error("Server failed to become ready", "name", name, "error", err)
46 slog.Error("Server failed to become ready", "name", name, "error", err)
47+ // Server never reached a ready state, but let's continue anyway, as
50− // We'll continue anyway, as some functionality might still work
48+ // some functionality might still work.
51 lspClient.SetServerState(lsp.StateError)
49 lspClient.SetServerState(lsp.StateError)
52 } else {
50 } else {
51+ // Server reached a ready state scuccessfully.
53 slog.Info("LSP server is ready", "name", name)
52 slog.Info("LSP server is ready", "name", name)
54 lspClient.SetServerState(lsp.StateReady)
53 lspClient.SetServerState(lsp.StateReady)
55 }
54 }
56
55
57 slog.Info("LSP client initialized", "name", name)
56 slog.Info("LSP client initialized", "name", name)
58
57
59− // Create a child context that can be canceled when the app is shutting down
58+ // Create a child context that can be canceled when the app is shutting
59+ // down.
60 watchCtx, cancelFunc := context.WithCancel(ctx)
60 watchCtx, cancelFunc := context.WithCancel(ctx)
61
61
62− // Create the workspace watcher
62+ // Create the workspace watcher.
63 workspaceWatcher := watcher.NewWorkspaceWatcher(name, lspClient)
63 workspaceWatcher := watcher.NewWorkspaceWatcher(name, lspClient)
64
64
65− // Store the cancel function to be called during cleanup
65+ // Store the cancel function to be called during cleanup.
66 app.cancelFuncsMutex.Lock()
66 app.cancelFuncsMutex.Lock()
67 app.watcherCancelFuncs = append(app.watcherCancelFuncs, cancelFunc)
67 app.watcherCancelFuncs = append(app.watcherCancelFuncs, cancelFunc)
68 app.cancelFuncsMutex.Unlock()
68 app.cancelFuncsMutex.Unlock()
69
69
70− // Add the watcher to a WaitGroup to track active goroutines
71− app.lspWatcherWG.Add(1)
72−
73 // Add to map with mutex protection before starting goroutine
70 // Add to map with mutex protection before starting goroutine
74 app.clientsMutex.Lock()
71 app.clientsMutex.Lock()
75 app.LSPClients[name] = lspClient
72 app.LSPClients[name] = lspClient
76 app.clientsMutex.Unlock()
73 app.clientsMutex.Unlock()
77
74
75+ // Run workspace watcher.
76+ app.lspWatcherWG.Add(1)
78 go app.runWorkspaceWatcher(watchCtx, name, workspaceWatcher)
77 go app.runWorkspaceWatcher(watchCtx, name, workspaceWatcher)
79}
78}
80
79
81−// runWorkspaceWatcher executes the workspace watcher for an LSP client
80+// runWorkspaceWatcher executes the workspace watcher for an LSP client.
82func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceWatcher *watcher.WorkspaceWatcher) {
81func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceWatcher *watcher.WorkspaceWatcher) {
83 defer app.lspWatcherWG.Done()
82 defer app.lspWatcherWG.Done()
84 defer log.RecoverPanic("LSP-"+name, func() {
83 defer log.RecoverPanic("LSP-"+name, func() {
85− // Try to restart the client
84+ // Try to restart the client.
86 app.restartLSPClient(ctx, name)
85 app.restartLSPClient(ctx, name)
87 })
86 })
88
87
@@ -90,31 +89,32 @@ func (app *App) runWorkspaceWatcher(ctx context.Context, name string, workspaceW
90 slog.Info("Workspace watcher stopped", "client", name)
89 slog.Info("Workspace watcher stopped", "client", name)
91}
90}
92
91
93−// restartLSPClient attempts to restart a crashed or failed LSP client
92+// restartLSPClient attempts to restart a crashed or failed LSP client.
94func (app *App) restartLSPClient(ctx context.Context, name string) {
93func (app *App) restartLSPClient(ctx context.Context, name string) {
95− // Get the original configuration
94+ // Get the original configuration.
96 clientConfig, exists := app.config.LSP[name]
95 clientConfig, exists := app.config.LSP[name]
97 if !exists {
96 if !exists {
98 slog.Error("Cannot restart client, configuration not found", "client", name)
97 slog.Error("Cannot restart client, configuration not found", "client", name)
99 return
98 return
100 }
99 }
101
100
102− // Clean up the old client if it exists
101+ // Clean up the old client if it exists.
103 app.clientsMutex.Lock()
102 app.clientsMutex.Lock()
104 oldClient, exists := app.LSPClients[name]
103 oldClient, exists := app.LSPClients[name]
105 if exists {
104 if exists {
106− delete(app.LSPClients, name) // Remove from map before potentially slow shutdown
105+ // Remove from map before potentially slow shutdown.
106+ delete(app.LSPClients, name)
107 }
107 }
108 app.clientsMutex.Unlock()
108 app.clientsMutex.Unlock()
109
109
110 if exists && oldClient != nil {
110 if exists && oldClient != nil {
111− // Try to shut it down gracefully, but don't block on errors
111+ // Try to shut down client gracefully, but don't block on errors.
112 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
112 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
113 _ = oldClient.Shutdown(shutdownCtx)
113 _ = oldClient.Shutdown(shutdownCtx)
114 cancel()
114 cancel()
115 }
115 }
116
116
117− // Create a new client using the shared function
117+ // Create a new client using the shared function.
118 app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
118 app.createAndStartLSPClient(ctx, name, clientConfig.Command, clientConfig.Args...)
119 slog.Info("Successfully restarted LSP client", "client", name)
119 slog.Info("Successfully restarted LSP client", "client", name)
120}
120}
internal/cmd/root.go+4−3
@@ -48,6 +48,7 @@ to assist developers in writing, debugging, and understanding code directly from
48 `,
48 `,
49 RunE: func(cmd *cobra.Command, args []string) error {
49 RunE: func(cmd *cobra.Command, args []string) error {
50 // Load the config
50 // Load the config
51+ // XXX: Handle errors.
51 debug, _ := cmd.Flags().GetBool("debug")
52 debug, _ := cmd.Flags().GetBool("debug")
52 cwd, _ := cmd.Flags().GetString("cwd")
53 cwd, _ := cmd.Flags().GetString("cwd")
53 prompt, _ := cmd.Flags().GetString("prompt")
54 prompt, _ := cmd.Flags().GetString("prompt")
@@ -76,7 +77,7 @@ to assist developers in writing, debugging, and understanding code directly from
76
77
77 ctx := cmd.Context()
78 ctx := cmd.Context()
78
79
79− // Connect DB, this will also run migrations
80+ // Connect to DB; this will also run migrations.
80 conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
81 conn, err := db.Connect(ctx, cfg.Options.DataDirectory)
81 if err != nil {
82 if err != nil {
82 return err
83 return err
@@ -95,13 +96,13 @@ to assist developers in writing, debugging, and understanding code directly from
95 return err
96 return err
96 }
97 }
97
98
98− // Non-interactive mode
99+ // Non-interactive mode.
99 if prompt != "" {
100 if prompt != "" {
100 // Run non-interactive flow using the App method
101 // Run non-interactive flow using the App method
101 return app.RunNonInteractive(ctx, prompt, quiet)
102 return app.RunNonInteractive(ctx, prompt, quiet)
102 }
103 }
103
104
104− // Set up the TUI
105+ // Set up the TUI.
105 program := tea.NewProgram(
106 program := tea.NewProgram(
106 tui.New(app),
107 tui.New(app),
107 tea.WithAltScreen(),
108 tea.WithAltScreen(),