app.go

  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}