From b36979592a1e04ad940c3fecf663c90706d7f6d7 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 15 Jan 2026 14:27:00 -0300 Subject: [PATCH] feat: properly handle file history Signed-off-by: Carlos Alexandro Becker --- internal/agent/tools/delete.go | 50 ++++++-- .../tui/components/chat/sidebar/sidebar.go | 118 ++++++++++++------ 2 files changed, 118 insertions(+), 50 deletions(-) diff --git a/internal/agent/tools/delete.go b/internal/agent/tools/delete.go index ab788a5ffdfefcec397c00cb29fa9074c6b19326..5a3229654bc17e3346d146fa810b95cedf7b5647 100644 --- a/internal/agent/tools/delete.go +++ b/internal/agent/tools/delete.go @@ -90,12 +90,14 @@ func NewDeleteTool(lspClients *csync.Map[string, *lsp.Client], permissions permi return fantasy.ToolResponse{}, permission.ErrorPermissionDenied } + // Save file content to history BEFORE deleting. + saveDeletedFileHistory(ctx, files, sessionID, filePath, isDir) + if err := os.RemoveAll(filePath); err != nil { return fantasy.ToolResponse{}, fmt.Errorf("error deleting path: %w", err) } lspCloseAndDeleteFiles(ctx, lspClients, filePath, isDir) - deleteFileHistory(ctx, files, sessionID, filePath, isDir) return fantasy.NewTextResponse(fmt.Sprintf("Successfully deleted: %s", filePath)), nil }) } @@ -136,21 +138,43 @@ func lspCloseAndDeleteFiles(ctx context.Context, lsps *csync.Map[string, *lsp.Cl } } -func deleteFileHistory(ctx context.Context, files history.Service, sessionID, filePath string, isDir bool) { - sessionFiles, err := files.ListLatestSessionFiles(ctx, sessionID) - if err != nil { +func saveDeletedFileHistory(ctx context.Context, files history.Service, sessionID, filePath string, isDir bool) { + if isDir { + // For directories, walk through and save all files. + _ = filepath.Walk(filePath, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return nil + } + saveFileBeforeDeletion(ctx, files, sessionID, path) + return nil + }) + } else { + // For single file. + saveFileBeforeDeletion(ctx, files, sessionID, filePath) + } +} + +func saveFileBeforeDeletion(ctx context.Context, files history.Service, sessionID, filePath string) { + // Check if file already exists in history. + existing, err := files.GetByPathAndSession(ctx, filePath, sessionID) + if err == nil && existing.Path != "" { + // File exists in history, create empty version to show deletion. + _, _ = files.CreateVersion(ctx, sessionID, filePath, "") return } - for _, file := range sessionFiles { - if !shouldDeletePath(file.Path, filePath, isDir) { - continue - } + // File not in history, read content and create initial + empty version. + content, err := os.ReadFile(filePath) + if err != nil { + return + } - fileEntry, err := files.GetByPathAndSession(ctx, file.Path, sessionID) - if err != nil { - continue - } - _ = files.Delete(ctx, fileEntry.ID) + // Create initial version with current content. + _, err = files.Create(ctx, sessionID, filePath, string(content)) + if err != nil { + return } + + // Create empty version to show deletion. + _, _ = files.CreateVersion(ctx, sessionID, filePath, "") } diff --git a/internal/tui/components/chat/sidebar/sidebar.go b/internal/tui/components/chat/sidebar/sidebar.go index 585e1754e3e515b58e3afa995a597e7d178ff79e..e1a927b6fd713b06a2512f271b134f04eb8ce0df 100644 --- a/internal/tui/components/chat/sidebar/sidebar.go +++ b/internal/tui/components/chat/sidebar/sidebar.go @@ -180,49 +180,93 @@ func (m *sidebarCmp) handleFileHistoryEvent(event pubsub.Event[history.File]) te file := event.Payload if event.Type == pubsub.DeletedEvent { - m.files.Del(file.Path) - return nil - } - found := false - for existing := range m.files.Seq() { - if existing.FilePath != file.Path { - continue - } - if existing.History.latestVersion.Version < file.Version { - existing.History.latestVersion = file - } else if file.Version == 0 { - existing.History.initialVersion = file - } else { - // If the version is not greater than the latest, we ignore it - continue - } - before, _ := fsext.ToUnixLineEndings(existing.History.initialVersion.Content) - after, _ := fsext.ToUnixLineEndings(existing.History.latestVersion.Content) - path := existing.History.initialVersion.Path - cwd := config.Get().WorkingDir() - path = strings.TrimPrefix(path, cwd) - _, additions, deletions := diff.GenerateDiff(before, after, path) - existing.Additions = additions - existing.Deletions = deletions - m.files.Set(file.Path, existing) - found = true - break + return m.handleFileDeleted(file) } - if found { + + existing, found := m.files.Get(file.Path) + if !found { + m.files.Set(file.Path, SessionFile{ + History: FileHistory{ + initialVersion: file, + latestVersion: file, + }, + FilePath: file.Path, + }) return nil } - sf := SessionFile{ - History: FileHistory{ - initialVersion: file, - latestVersion: file, - }, - FilePath: file.Path, - Additions: 0, - Deletions: 0, + + if !m.shouldUpdateFileVersion(existing, file) { + return nil } - m.files.Set(file.Path, sf) + + m.updateFileVersion(&existing, file) + m.recalculateFileDiff(&existing) + m.files.Set(file.Path, existing) + return nil + } +} + +func (m *sidebarCmp) handleFileDeleted(file history.File) tea.Msg { + existing, found := m.files.Get(file.Path) + if !found { return nil } + + if existing.History.initialVersion.Content == "" { + m.files.Del(file.Path) + return nil + } + + existing.History.latestVersion = history.File{ + ID: file.ID, + SessionID: file.SessionID, + Path: file.Path, + Content: "", + Version: file.Version, + CreatedAt: file.CreatedAt, + UpdatedAt: file.UpdatedAt, + } + + m.recalculateFileDiff(&existing) + m.files.Set(file.Path, existing) + return nil +} + +func (m *sidebarCmp) shouldUpdateFileVersion(existing SessionFile, file history.File) bool { + if existing.FilePath != file.Path { + return false + } + + if existing.History.latestVersion.Version < file.Version { + return true + } + + if file.Version == 0 { + return true + } + + return false +} + +func (m *sidebarCmp) updateFileVersion(existing *SessionFile, file history.File) { + if existing.History.latestVersion.Version < file.Version { + existing.History.latestVersion = file + } else if file.Version == 0 { + existing.History.initialVersion = file + } +} + +func (m *sidebarCmp) recalculateFileDiff(existing *SessionFile) { + before, _ := fsext.ToUnixLineEndings(existing.History.initialVersion.Content) + after, _ := fsext.ToUnixLineEndings(existing.History.latestVersion.Content) + + path := existing.History.initialVersion.Path + cwd := config.Get().WorkingDir() + path = strings.TrimPrefix(path, cwd) + + _, additions, deletions := diff.GenerateDiff(before, after, path) + existing.Additions = additions + existing.Deletions = deletions } func (m *sidebarCmp) loadSessionFiles() tea.Msg {