@@ -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, "")
}
@@ -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 {