1package app
2
3import (
4 "context"
5 "database/sql"
6 "maps"
7 "sync"
8 "time"
9
10 "github.com/opencode-ai/opencode/internal/config"
11 "github.com/opencode-ai/opencode/internal/db"
12 "github.com/opencode-ai/opencode/internal/history"
13 "github.com/opencode-ai/opencode/internal/llm/agent"
14 "github.com/opencode-ai/opencode/internal/logging"
15 "github.com/opencode-ai/opencode/internal/lsp"
16 "github.com/opencode-ai/opencode/internal/message"
17 "github.com/opencode-ai/opencode/internal/permission"
18 "github.com/opencode-ai/opencode/internal/session"
19 "github.com/opencode-ai/opencode/internal/tui/theme"
20)
21
22type App struct {
23 Sessions session.Service
24 Messages message.Service
25 History history.Service
26 Permissions permission.Service
27
28 CoderAgent agent.Service
29
30 LSPClients map[string]*lsp.Client
31
32 clientsMutex sync.RWMutex
33
34 watcherCancelFuncs []context.CancelFunc
35 cancelFuncsMutex sync.Mutex
36 watcherWG sync.WaitGroup
37}
38
39func New(ctx context.Context, conn *sql.DB) (*App, error) {
40 q := db.New(conn)
41 sessions := session.NewService(q)
42 messages := message.NewService(q)
43 files := history.NewService(q, conn)
44
45 app := &App{
46 Sessions: sessions,
47 Messages: messages,
48 History: files,
49 Permissions: permission.NewPermissionService(),
50 LSPClients: make(map[string]*lsp.Client),
51 }
52
53 // Initialize theme based on configuration
54 app.initTheme()
55
56 // Initialize LSP clients in the background
57 go app.initLSPClients(ctx)
58
59 var err error
60 app.CoderAgent, err = agent.NewAgent(
61 config.AgentCoder,
62 app.Sessions,
63 app.Messages,
64 agent.CoderAgentTools(
65 app.Permissions,
66 app.Sessions,
67 app.Messages,
68 app.History,
69 app.LSPClients,
70 ),
71 )
72 if err != nil {
73 logging.Error("Failed to create coder agent", err)
74 return nil, err
75 }
76
77 return app, nil
78}
79
80// initTheme sets the application theme based on the configuration
81func (app *App) initTheme() {
82 cfg := config.Get()
83 if cfg == nil || cfg.TUI.Theme == "" {
84 return // Use default theme
85 }
86
87 // Try to set the theme from config
88 err := theme.SetTheme(cfg.TUI.Theme)
89 if err != nil {
90 logging.Warn("Failed to set theme from config, using default theme", "theme", cfg.TUI.Theme, "error", err)
91 } else {
92 logging.Debug("Set theme from config", "theme", cfg.TUI.Theme)
93 }
94}
95
96// Shutdown performs a clean shutdown of the application
97func (app *App) Shutdown() {
98 // Cancel all watcher goroutines
99 app.cancelFuncsMutex.Lock()
100 for _, cancel := range app.watcherCancelFuncs {
101 cancel()
102 }
103 app.cancelFuncsMutex.Unlock()
104 app.watcherWG.Wait()
105
106 // Perform additional cleanup for LSP clients
107 app.clientsMutex.RLock()
108 clients := make(map[string]*lsp.Client, len(app.LSPClients))
109 maps.Copy(clients, app.LSPClients)
110 app.clientsMutex.RUnlock()
111
112 for name, client := range clients {
113 shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
114 if err := client.Shutdown(shutdownCtx); err != nil {
115 logging.Error("Failed to shutdown LSP client", "name", name, "error", err)
116 }
117 cancel()
118 }
119}