diff options
| author | adamdottv <[email protected]> | 2025-05-29 15:22:25 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-05-29 15:22:25 -0500 |
| commit | 1c01ee48340c524af9223fac43f21d3a545e4583 (patch) | |
| tree | f33bde056ef0d65828f15a9199ad0b910f65d74f /internal | |
| parent | 005d6e0bde9a42e2bebee7b712b0fe9a7be23499 (diff) | |
| download | opencode-1c01ee48340c524af9223fac43f21d3a545e4583.tar.gz opencode-1c01ee48340c524af9223fac43f21d3a545e4583.zip | |
wip: refactoring tui
Diffstat (limited to 'internal')
| -rw-r--r-- | internal/history/history.go | 441 | ||||
| -rw-r--r-- | internal/logging/logging.go | 292 | ||||
| -rw-r--r-- | internal/tui/components/chat/sidebar.go | 74 |
3 files changed, 1 insertions, 806 deletions
diff --git a/internal/history/history.go b/internal/history/history.go deleted file mode 100644 index 12c94a391..000000000 --- a/internal/history/history.go +++ /dev/null @@ -1,441 +0,0 @@ -package history - -import ( - "context" - "database/sql" - "fmt" - "log/slog" - "slices" - "strconv" - "strings" - "sync" - "time" - - "github.com/google/uuid" - "github.com/sst/opencode/internal/db" - "github.com/sst/opencode/internal/pubsub" -) - -const ( - InitialVersion = "initial" -) - -type File struct { - ID string - SessionID string - Path string - Content string - Version string - CreatedAt time.Time - UpdatedAt time.Time -} - -const ( - EventFileCreated pubsub.EventType = "history_file_created" - EventFileVersionCreated pubsub.EventType = "history_file_version_created" - EventFileUpdated pubsub.EventType = "history_file_updated" - EventFileDeleted pubsub.EventType = "history_file_deleted" - EventSessionFilesDeleted pubsub.EventType = "history_session_files_deleted" -) - -type Service interface { - pubsub.Subscriber[File] - - Create(ctx context.Context, sessionID, path, content string) (File, error) - CreateVersion(ctx context.Context, sessionID, path, content string) (File, error) - Get(ctx context.Context, id string) (File, error) - GetByPathAndVersion(ctx context.Context, sessionID, path, version string) (File, error) - GetLatestByPathAndSession(ctx context.Context, path, sessionID string) (File, error) - ListBySession(ctx context.Context, sessionID string) ([]File, error) - ListLatestSessionFiles(ctx context.Context, sessionID string) ([]File, error) - ListVersionsByPath(ctx context.Context, path string) ([]File, error) - Update(ctx context.Context, file File) (File, error) - Delete(ctx context.Context, id string) error - DeleteSessionFiles(ctx context.Context, sessionID string) error -} - -type service struct { - db *db.Queries - sqlDB *sql.DB - broker *pubsub.Broker[File] - mu sync.RWMutex -} - -var globalHistoryService *service - -func InitService(sqlDatabase *sql.DB) error { - if globalHistoryService != nil { - return fmt.Errorf("history service already initialized") - } - queries := db.New(sqlDatabase) - broker := pubsub.NewBroker[File]() - - globalHistoryService = &service{ - db: queries, - sqlDB: sqlDatabase, - broker: broker, - } - return nil -} - -func GetService() Service { - if globalHistoryService == nil { - panic("history service not initialized. Call history.InitService() first.") - } - return globalHistoryService -} - -func (s *service) Create(ctx context.Context, sessionID, path, content string) (File, error) { - return s.createWithVersion(ctx, sessionID, path, content, InitialVersion, EventFileCreated) -} - -func (s *service) CreateVersion(ctx context.Context, sessionID, path, content string) (File, error) { - s.mu.RLock() - files, err := s.db.ListFilesByPath(ctx, path) - s.mu.RUnlock() - - if err != nil && err != sql.ErrNoRows { - return File{}, fmt.Errorf("db.ListFilesByPath for next version: %w", err) - } - - latestVersionNumber := 0 - if len(files) > 0 { - // Sort to be absolutely sure about the latest version globally for this path - slices.SortFunc(files, func(a, b db.File) int { - if strings.HasPrefix(a.Version, "v") && strings.HasPrefix(b.Version, "v") { - vA, _ := strconv.Atoi(a.Version[1:]) - vB, _ := strconv.Atoi(b.Version[1:]) - return vB - vA // Descending to get latest first - } - if a.Version == InitialVersion && b.Version != InitialVersion { - return 1 // initial comes after vX - } - if b.Version == InitialVersion && a.Version != InitialVersion { - return -1 - } - // Compare timestamps as strings (ISO format sorts correctly) - if b.CreatedAt > a.CreatedAt { - return 1 - } else if a.CreatedAt > b.CreatedAt { - return -1 - } - return 0 // Equal timestamps - }) - - latestFile := files[0] - if strings.HasPrefix(latestFile.Version, "v") { - vNum, parseErr := strconv.Atoi(latestFile.Version[1:]) - if parseErr == nil { - latestVersionNumber = vNum - } - } - } - nextVersionStr := fmt.Sprintf("v%d", latestVersionNumber+1) - return s.createWithVersion(ctx, sessionID, path, content, nextVersionStr, EventFileVersionCreated) -} - -func (s *service) createWithVersion(ctx context.Context, sessionID, path, content, version string, eventType pubsub.EventType) (File, error) { - s.mu.Lock() - defer s.mu.Unlock() - - const maxRetries = 3 - var file File - var err error - - for attempt := range maxRetries { - tx, txErr := s.sqlDB.BeginTx(ctx, nil) - if txErr != nil { - return File{}, fmt.Errorf("failed to begin transaction: %w", txErr) - } - qtx := s.db.WithTx(tx) - - dbFile, createErr := qtx.CreateFile(ctx, db.CreateFileParams{ - ID: uuid.New().String(), - SessionID: sessionID, - Path: path, - Content: content, - Version: version, - }) - - if createErr != nil { - if rbErr := tx.Rollback(); rbErr != nil { - slog.Error("Failed to rollback transaction on create error", "error", rbErr) - } - if strings.Contains(createErr.Error(), "UNIQUE constraint failed: files.path, files.session_id, files.version") { - if attempt < maxRetries-1 { - slog.Warn("Unique constraint violation for file version, retrying with incremented version", "path", path, "session", sessionID, "attempted_version", version, "attempt", attempt+1) - // Increment version string like v1, v2, v3... - if strings.HasPrefix(version, "v") { - numPart := version[1:] - num, parseErr := strconv.Atoi(numPart) - if parseErr == nil { - version = fmt.Sprintf("v%d", num+1) - continue // Retry with new version - } - } - // Fallback if version is not "vX" or parsing failed - version = fmt.Sprintf("%s-retry%d", version, attempt+1) - continue - } - } - return File{}, fmt.Errorf("db.CreateFile within transaction: %w", createErr) - } - - if commitErr := tx.Commit(); commitErr != nil { - return File{}, fmt.Errorf("failed to commit transaction: %w", commitErr) - } - - file = s.fromDBItem(dbFile) - s.broker.Publish(eventType, file) - return file, nil // Success - } - - return File{}, fmt.Errorf("failed to create file after %d retries due to version conflicts: %w", maxRetries, err) -} - -func (s *service) Get(ctx context.Context, id string) (File, error) { - s.mu.RLock() - defer s.mu.RUnlock() - dbFile, err := s.db.GetFile(ctx, id) - if err != nil { - if err == sql.ErrNoRows { - return File{}, fmt.Errorf("file with ID '%s' not found", id) - } - return File{}, fmt.Errorf("db.GetFile: %w", err) - } - return s.fromDBItem(dbFile), nil -} - -func (s *service) GetByPathAndVersion(ctx context.Context, sessionID, path, version string) (File, error) { - s.mu.RLock() - defer s.mu.RUnlock() - - // sqlc doesn't directly support GetyByPathAndVersionAndSession - // We list and filter. This could be optimized with a custom query if performance is an issue. - allFilesForPath, err := s.db.ListFilesByPath(ctx, path) - if err != nil { - return File{}, fmt.Errorf("db.ListFilesByPath for GetByPathAndVersion: %w", err) - } - - for _, dbFile := range allFilesForPath { - if dbFile.SessionID == sessionID && dbFile.Version == version { - return s.fromDBItem(dbFile), nil - } - } - return File{}, fmt.Errorf("file not found for session '%s', path '%s', version '%s'", sessionID, path, version) -} - -func (s *service) GetLatestByPathAndSession(ctx context.Context, path, sessionID string) (File, error) { - s.mu.RLock() - defer s.mu.RUnlock() - // GetFileByPathAndSession in sqlc already orders by created_at DESC and takes LIMIT 1 - dbFile, err := s.db.GetFileByPathAndSession(ctx, db.GetFileByPathAndSessionParams{ - Path: path, - SessionID: sessionID, - }) - if err != nil { - if err == sql.ErrNoRows { - return File{}, fmt.Errorf("no file found for path '%s' in session '%s'", path, sessionID) - } - return File{}, fmt.Errorf("db.GetFileByPathAndSession: %w", err) - } - return s.fromDBItem(dbFile), nil -} - -func (s *service) ListBySession(ctx context.Context, sessionID string) ([]File, error) { - s.mu.RLock() - defer s.mu.RUnlock() - dbFiles, err := s.db.ListFilesBySession(ctx, sessionID) // Assumes this orders by created_at ASC - if err != nil { - return nil, fmt.Errorf("db.ListFilesBySession: %w", err) - } - files := make([]File, len(dbFiles)) - for i, dbF := range dbFiles { - files[i] = s.fromDBItem(dbF) - } - return files, nil -} - -func (s *service) ListLatestSessionFiles(ctx context.Context, sessionID string) ([]File, error) { - s.mu.RLock() - defer s.mu.RUnlock() - dbFiles, err := s.db.ListLatestSessionFiles(ctx, sessionID) // Uses the specific sqlc query - if err != nil { - return nil, fmt.Errorf("db.ListLatestSessionFiles: %w", err) - } - files := make([]File, len(dbFiles)) - for i, dbF := range dbFiles { - files[i] = s.fromDBItem(dbF) - } - return files, nil -} - -func (s *service) ListVersionsByPath(ctx context.Context, path string) ([]File, error) { - s.mu.RLock() - defer s.mu.RUnlock() - dbFiles, err := s.db.ListFilesByPath(ctx, path) // sqlc query orders by created_at DESC - if err != nil { - return nil, fmt.Errorf("db.ListFilesByPath: %w", err) - } - files := make([]File, len(dbFiles)) - for i, dbF := range dbFiles { - files[i] = s.fromDBItem(dbF) - } - return files, nil -} - -func (s *service) Update(ctx context.Context, file File) (File, error) { - s.mu.Lock() - defer s.mu.Unlock() - - if file.ID == "" { - return File{}, fmt.Errorf("cannot update file with empty ID") - } - // UpdatedAt is handled by DB trigger - dbFile, err := s.db.UpdateFile(ctx, db.UpdateFileParams{ - ID: file.ID, - Content: file.Content, - Version: file.Version, - }) - if err != nil { - return File{}, fmt.Errorf("db.UpdateFile: %w", err) - } - updatedFile := s.fromDBItem(dbFile) - s.broker.Publish(EventFileUpdated, updatedFile) - return updatedFile, nil -} - -func (s *service) Delete(ctx context.Context, id string) error { - s.mu.Lock() - fileToPublish, err := s.getServiceForPublish(ctx, id) // Use internal method with appropriate locking - s.mu.Unlock() - - if err != nil { - if strings.Contains(err.Error(), "not found") { - slog.Warn("Attempted to delete non-existent file history", "id", id) - return nil // Or return specific error if needed - } - return err - } - - s.mu.Lock() - defer s.mu.Unlock() - err = s.db.DeleteFile(ctx, id) - if err != nil { - return fmt.Errorf("db.DeleteFile: %w", err) - } - if fileToPublish != nil { - s.broker.Publish(EventFileDeleted, *fileToPublish) - } - return nil -} - -func (s *service) getServiceForPublish(ctx context.Context, id string) (*File, error) { - // Assumes outer lock is NOT held or caller manages it. - // For GetFile, it has its own RLock. - dbFile, err := s.db.GetFile(ctx, id) - if err != nil { - return nil, err - } - file := s.fromDBItem(dbFile) - return &file, nil -} - -func (s *service) DeleteSessionFiles(ctx context.Context, sessionID string) error { - s.mu.Lock() // Lock for the entire operation - defer s.mu.Unlock() - - // Get files first for publishing events - filesToDelete, err := s.db.ListFilesBySession(ctx, sessionID) - if err != nil { - return fmt.Errorf("db.ListFilesBySession for deletion: %w", err) - } - - err = s.db.DeleteSessionFiles(ctx, sessionID) - if err != nil { - return fmt.Errorf("db.DeleteSessionFiles: %w", err) - } - - for _, dbFile := range filesToDelete { - file := s.fromDBItem(dbFile) - s.broker.Publish(EventFileDeleted, file) // Individual delete events - } - return nil -} - -func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[File] { - return s.broker.Subscribe(ctx) -} - -func (s *service) fromDBItem(item db.File) File { - // Parse timestamps from ISO strings - createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt) - if err != nil { - slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err) - createdAt = time.Now() // Fallback - } - - updatedAt, err := time.Parse(time.RFC3339Nano, item.UpdatedAt) - if err != nil { - slog.Error("Failed to parse created_at", "value", item.CreatedAt, "error", err) - updatedAt = time.Now() // Fallback - } - - return File{ - ID: item.ID, - SessionID: item.SessionID, - Path: item.Path, - Content: item.Content, - Version: item.Version, - CreatedAt: createdAt, - UpdatedAt: updatedAt, - } -} - -func Create(ctx context.Context, sessionID, path, content string) (File, error) { - return GetService().Create(ctx, sessionID, path, content) -} - -func CreateVersion(ctx context.Context, sessionID, path, content string) (File, error) { - return GetService().CreateVersion(ctx, sessionID, path, content) -} - -func Get(ctx context.Context, id string) (File, error) { - return GetService().Get(ctx, id) -} - -func GetByPathAndVersion(ctx context.Context, sessionID, path, version string) (File, error) { - return GetService().GetByPathAndVersion(ctx, sessionID, path, version) -} - -func GetLatestByPathAndSession(ctx context.Context, path, sessionID string) (File, error) { - return GetService().GetLatestByPathAndSession(ctx, path, sessionID) -} - -func ListBySession(ctx context.Context, sessionID string) ([]File, error) { - return GetService().ListBySession(ctx, sessionID) -} - -func ListLatestSessionFiles(ctx context.Context, sessionID string) ([]File, error) { - return GetService().ListLatestSessionFiles(ctx, sessionID) -} - -func ListVersionsByPath(ctx context.Context, path string) ([]File, error) { - return GetService().ListVersionsByPath(ctx, path) -} - -func Update(ctx context.Context, file File) (File, error) { - return GetService().Update(ctx, file) -} - -func Delete(ctx context.Context, id string) error { - return GetService().Delete(ctx, id) -} - -func DeleteSessionFiles(ctx context.Context, sessionID string) error { - return GetService().DeleteSessionFiles(ctx, sessionID) -} - -func Subscribe(ctx context.Context) <-chan pubsub.Event[File] { - return GetService().Subscribe(ctx) -} diff --git a/internal/logging/logging.go b/internal/logging/logging.go deleted file mode 100644 index 2ba426756..000000000 --- a/internal/logging/logging.go +++ /dev/null @@ -1,292 +0,0 @@ -package logging - -import ( - "bytes" - "context" - "database/sql" - "encoding/json" - "fmt" - "io" - "log/slog" - "os" - "runtime/debug" - "strings" - "time" - - "github.com/go-logfmt/logfmt" - "github.com/google/uuid" - "github.com/sst/opencode/internal/db" - "github.com/sst/opencode/internal/pubsub" -) - -type Log struct { - ID string - SessionID string - Timestamp time.Time - Level string - Message string - Attributes map[string]string - CreatedAt time.Time -} - -const ( - EventLogCreated pubsub.EventType = "log_created" -) - -type Service interface { - pubsub.Subscriber[Log] - - Create(ctx context.Context, timestamp time.Time, level, message string, attributes map[string]string, sessionID string) error - ListBySession(ctx context.Context, sessionID string) ([]Log, error) - ListAll(ctx context.Context, limit int) ([]Log, error) -} - -type service struct { - db *db.Queries - broker *pubsub.Broker[Log] -} - -var globalLoggingService *service - -func InitService(dbConn *sql.DB) error { - if globalLoggingService != nil { - return fmt.Errorf("logging service already initialized") - } - queries := db.New(dbConn) - broker := pubsub.NewBroker[Log]() - - globalLoggingService = &service{ - db: queries, - broker: broker, - } - return nil -} - -func GetService() Service { - if globalLoggingService == nil { - panic("logging service not initialized. Call logging.InitService() first.") - } - return globalLoggingService -} - -func (s *service) Create(ctx context.Context, timestamp time.Time, level, message string, attributes map[string]string, sessionID string) error { - if level == "" { - level = "info" - } - - var attributesJSON sql.NullString - if len(attributes) > 0 { - attributesBytes, err := json.Marshal(attributes) - if err != nil { - return fmt.Errorf("failed to marshal log attributes: %w", err) - } - attributesJSON = sql.NullString{String: string(attributesBytes), Valid: true} - } - - dbLog, err := s.db.CreateLog(ctx, db.CreateLogParams{ - ID: uuid.New().String(), - SessionID: sql.NullString{String: sessionID, Valid: sessionID != ""}, - Timestamp: timestamp.UTC().Format(time.RFC3339Nano), - Level: level, - Message: message, - Attributes: attributesJSON, - }) - - if err != nil { - return fmt.Errorf("db.CreateLog: %w", err) - } - - log := s.fromDBItem(dbLog) - s.broker.Publish(EventLogCreated, log) - return nil -} - -func (s *service) ListBySession(ctx context.Context, sessionID string) ([]Log, error) { - dbLogs, err := s.db.ListLogsBySession(ctx, sql.NullString{String: sessionID, Valid: true}) - if err != nil { - return nil, fmt.Errorf("db.ListLogsBySession: %w", err) - } - - logs := make([]Log, len(dbLogs)) - for i, dbSess := range dbLogs { - logs[i] = s.fromDBItem(dbSess) - } - return logs, nil -} - -func (s *service) ListAll(ctx context.Context, limit int) ([]Log, error) { - dbLogs, err := s.db.ListAllLogs(ctx, int64(limit)) - if err != nil { - return nil, fmt.Errorf("db.ListAllLogs: %w", err) - } - logs := make([]Log, len(dbLogs)) - for i, dbSess := range dbLogs { - logs[i] = s.fromDBItem(dbSess) - } - return logs, nil -} - -func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[Log] { - return s.broker.Subscribe(ctx) -} - -func (s *service) fromDBItem(item db.Log) Log { - log := Log{ - ID: item.ID, - SessionID: item.SessionID.String, - Level: item.Level, - Message: item.Message, - } - - // Parse timestamp from ISO string - timestamp, err := time.Parse(time.RFC3339Nano, item.Timestamp) - if err == nil { - log.Timestamp = timestamp - } else { - log.Timestamp = time.Now() // Fallback - } - - // Parse created_at from ISO string - createdAt, err := time.Parse(time.RFC3339Nano, item.CreatedAt) - if err == nil { - log.CreatedAt = createdAt - } else { - log.CreatedAt = time.Now() // Fallback - } - - if item.Attributes.Valid && item.Attributes.String != "" { - if err := json.Unmarshal([]byte(item.Attributes.String), &log.Attributes); err != nil { - slog.Error("Failed to unmarshal log attributes", "log_id", item.ID, "error", err) - log.Attributes = make(map[string]string) - } - } else { - log.Attributes = make(map[string]string) - } - - return log -} - -func Create(ctx context.Context, timestamp time.Time, level, message string, attributes map[string]string, sessionID string) error { - return GetService().Create(ctx, timestamp, level, message, attributes, sessionID) -} - -func ListBySession(ctx context.Context, sessionID string) ([]Log, error) { - return GetService().ListBySession(ctx, sessionID) -} - -func ListAll(ctx context.Context, limit int) ([]Log, error) { - return GetService().ListAll(ctx, limit) -} - -func Subscribe(ctx context.Context) <-chan pubsub.Event[Log] { - return GetService().Subscribe(ctx) -} - -type slogWriter struct{} - -func (sw *slogWriter) Write(p []byte) (n int, err error) { - // Example: time=2024-05-09T12:34:56.789-05:00 level=INFO msg="User request" session=xyz foo=bar - d := logfmt.NewDecoder(bytes.NewReader(p)) - for d.ScanRecord() { - var timestamp time.Time - var level string - var message string - var sessionID string - var attributes map[string]string - - attributes = make(map[string]string) - hasTimestamp := false - - for d.ScanKeyval() { - key := string(d.Key()) - value := string(d.Value()) - - switch key { - case "time": - parsedTime, timeErr := time.Parse(time.RFC3339Nano, value) - if timeErr != nil { - parsedTime, timeErr = time.Parse(time.RFC3339, value) - if timeErr != nil { - slog.Error("Failed to parse time in slog writer", "value", value, "error", timeErr) - timestamp = time.Now().UTC() - hasTimestamp = true - continue - } - } - timestamp = parsedTime - hasTimestamp = true - case "level": - level = strings.ToLower(value) - case "msg", "message": - message = value - case "session_id": - sessionID = value - default: - attributes[key] = value - } - } - if d.Err() != nil { - return len(p), fmt.Errorf("logfmt.ScanRecord: %w", d.Err()) - } - - if !hasTimestamp { - timestamp = time.Now() - } - - // Create log entry via the service (non-blocking or handle error appropriately) - // Using context.Background() as this is a low-level logging write. - go func(timestamp time.Time, level, message string, attributes map[string]string, sessionID string) { // Run in a goroutine to avoid blocking slog - if globalLoggingService == nil { - // If the logging service is not initialized, log the message to stderr - // fmt.Fprintf(os.Stderr, "ERROR [logging.slogWriter]: logging service not initialized\n") - return - } - if err := Create(context.Background(), timestamp, level, message, attributes, sessionID); err != nil { - // Log internal error using a more primitive logger to avoid loops - fmt.Fprintf(os.Stderr, "ERROR [logging.slogWriter]: failed to persist log: %v\n", err) - } - }(timestamp, level, message, attributes, sessionID) - } - if d.Err() != nil { - return len(p), fmt.Errorf("logfmt.ScanRecord final: %w", d.Err()) - } - return len(p), nil -} - -func NewSlogWriter() io.Writer { - return &slogWriter{} -} - -// RecoverPanic is a common function to handle panics gracefully. -// It logs the error, creates a panic log file with stack trace, -// and executes an optional cleanup function. -func RecoverPanic(name string, cleanup func()) { - if r := recover(); r != nil { - errorMsg := fmt.Sprintf("Panic in %s: %v", name, r) - // Use slog directly here, as our service might be the one panicking. - slog.Error(errorMsg) - // status.Error(errorMsg) - - timestamp := time.Now().Format("20060102-150405") - filename := fmt.Sprintf("opencode-panic-%s-%s.log", name, timestamp) - - file, err := os.Create(filename) - if err != nil { - errMsg := fmt.Sprintf("Failed to create panic log file '%s': %v", filename, err) - slog.Error(errMsg) - // status.Error(errMsg) - } else { - defer file.Close() - fmt.Fprintf(file, "Panic in %s: %v\n\n", name, r) - fmt.Fprintf(file, "Time: %s\n\n", time.Now().Format(time.RFC3339)) - fmt.Fprintf(file, "Stack Trace:\n%s\n", string(debug.Stack())) // Capture stack trace - infoMsg := fmt.Sprintf("Panic details written to %s", filename) - slog.Info(infoMsg) - // status.Info(infoMsg) - } - - if cleanup != nil { - cleanup() - } - } -} diff --git a/internal/tui/components/chat/sidebar.go b/internal/tui/components/chat/sidebar.go index cbce35753..1f038a9b0 100644 --- a/internal/tui/components/chat/sidebar.go +++ b/internal/tui/components/chat/sidebar.go @@ -10,9 +10,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/sst/opencode/internal/config" "github.com/sst/opencode/internal/tui/app" - // "github.com/sst/opencode/internal/diff" - "github.com/sst/opencode/internal/history" - "github.com/sst/opencode/internal/pubsub" "github.com/sst/opencode/internal/tui/state" "github.com/sst/opencode/internal/tui/styles" "github.com/sst/opencode/internal/tui/theme" @@ -43,7 +40,7 @@ func (m *sidebarCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // TODO: History service not implemented in API yet // ctx := context.Background() // m.loadModifiedFiles(ctx) - case pubsub.Event[history.File]: + // case pubsub.Event[history.File]: // TODO: History service not implemented in API yet // if msg.Payload.SessionID == m.app.CurrentSession.ID { // // Process the individual file change instead of reloading all files @@ -283,75 +280,6 @@ func (m *sidebarCmp) loadModifiedFiles(ctx context.Context) { */ } -func (m *sidebarCmp) processFileChanges(ctx context.Context, file history.File) { - // TODO: History service not implemented in API yet - return - /* - // Skip if this is the initial version (no changes to show) - if file.Version == history.InitialVersion { - return - } - - // Find the initial version for this file - initialVersion, err := m.findInitialVersion(ctx, file.Path) - if err != nil || initialVersion.ID == "" { - return - } - - // Skip if content hasn't changed - if initialVersion.Content == file.Content { - // If this file was previously modified but now matches the initial version, - // remove it from the modified files list - displayPath := getDisplayPath(file.Path) - delete(m.modFiles, displayPath) - return - } - - // Calculate diff between initial and latest version - _, additions, removals := diff.GenerateDiff(initialVersion.Content, file.Content, file.Path) - - // Only add to modified files if there are changes - if additions > 0 || removals > 0 { - displayPath := getDisplayPath(file.Path) - m.modFiles[displayPath] = struct { - additions int - removals int - }{ - additions: additions, - removals: removals, - } - } else { - // If no changes, remove from modified files - displayPath := getDisplayPath(file.Path) - delete(m.modFiles, displayPath) - } - */ -} - -// Helper function to find the initial version of a file -func (m *sidebarCmp) findInitialVersion(ctx context.Context, path string) (history.File, error) { - // TODO: History service not implemented in API yet - return history.File{}, fmt.Errorf("history service not implemented") - /* - // Get all versions of this file for the session - fileVersions, err := m.app.History.ListBySession(ctx, m.app.CurrentSession.ID) - if err != nil { - return history.File{}, err - } - */ - - /* - // Find the initial version - for _, v := range fileVersions { - if v.Path == path && v.Version == history.InitialVersion { - return v, nil - } - } - - return history.File{}, fmt.Errorf("initial version not found") - */ -} - // Helper function to get the display path for a file func getDisplayPath(path string) string { workingDir := config.WorkingDirectory() |
