From f79b55a71ac713043480d6a95c496cc5337752a0 Mon Sep 17 00:00:00 2001 From: Carlos Alexandro Becker Date: Thu, 15 Jan 2026 13:25:57 -0300 Subject: [PATCH] fix: improvements Signed-off-by: Carlos Alexandro Becker --- internal/agent/coordinator.go | 2 +- internal/agent/tools/delete.go | 54 ++++++++++++++----- internal/lsp/client.go | 15 ++++++ .../tui/components/chat/sidebar/sidebar.go | 5 ++ 4 files changed, 61 insertions(+), 15 deletions(-) diff --git a/internal/agent/coordinator.go b/internal/agent/coordinator.go index dc1485709aba82b9504ce26ed5d3b32e363c2eea..dbc9b822db0c0d29fa616303c91199de666c716d 100644 --- a/internal/agent/coordinator.go +++ b/internal/agent/coordinator.go @@ -390,7 +390,7 @@ func (c *coordinator) buildTools(ctx context.Context, agent config.Agent) ([]fan tools.NewDownloadTool(c.permissions, c.cfg.WorkingDir(), nil), tools.NewEditTool(c.lspClients, c.permissions, c.history, c.cfg.WorkingDir()), tools.NewMultiEditTool(c.lspClients, c.permissions, c.history, c.cfg.WorkingDir()), - tools.NewDeleteTool(c.lspClients, c.permissions, c.cfg.WorkingDir()), + tools.NewDeleteTool(c.lspClients, c.permissions, c.history, c.cfg.WorkingDir()), tools.NewFetchTool(c.permissions, c.cfg.WorkingDir(), nil), tools.NewGlobTool(c.cfg.WorkingDir()), tools.NewGrepTool(c.cfg.WorkingDir()), diff --git a/internal/agent/tools/delete.go b/internal/agent/tools/delete.go index 677fb5b0223913ff9ac9826551e06cd5d7d31d56..ab788a5ffdfefcec397c00cb29fa9074c6b19326 100644 --- a/internal/agent/tools/delete.go +++ b/internal/agent/tools/delete.go @@ -12,6 +12,7 @@ import ( "github.com/charmbracelet/crush/internal/csync" "github.com/charmbracelet/crush/internal/filepathext" "github.com/charmbracelet/crush/internal/fsext" + "github.com/charmbracelet/crush/internal/history" "github.com/charmbracelet/crush/internal/lsp" "github.com/charmbracelet/crush/internal/permission" "github.com/charmbracelet/x/powernap/pkg/lsp/protocol" @@ -37,7 +38,7 @@ type DeletePermissionsParams struct { const DeleteToolName = "delete" // NewDeleteTool creates a new delete tool. -func NewDeleteTool(lspClients *csync.Map[string, *lsp.Client], permissions permission.Service, workingDir string) fantasy.AgentTool { +func NewDeleteTool(lspClients *csync.Map[string, *lsp.Client], permissions permission.Service, files history.Service, workingDir string) fantasy.AgentTool { return fantasy.NewAgentTool( DeleteToolName, string(deleteDescription), @@ -93,7 +94,8 @@ func NewDeleteTool(lspClients *csync.Map[string, *lsp.Client], permissions permi return fantasy.ToolResponse{}, fmt.Errorf("error deleting path: %w", err) } - lspCloseAndDeleteFiles(ctx, lspClients, filePath) + lspCloseAndDeleteFiles(ctx, lspClients, filePath, isDir) + deleteFileHistory(ctx, files, sessionID, filePath, isDir) return fantasy.NewTextResponse(fmt.Sprintf("Successfully deleted: %s", filePath)), nil }) } @@ -105,26 +107,50 @@ func buildDeleteDescription(filePath string, isDir bool) string { return fmt.Sprintf("Delete directory %s and all its contents", filePath) } -func lspCloseAndDeleteFiles(ctx context.Context, lsps *csync.Map[string, *lsp.Client], filePath string) { - cleanPath := filepath.Clean(filePath) +// shouldDeletePath checks if a path matches the deletion target. +// For files, it matches exact paths. For directories, it matches the directory +// and all paths within it. +func shouldDeletePath(path, targetPath string, isDir bool) bool { + cleanPath := filepath.Clean(path) + cleanTarget := filepath.Clean(targetPath) + + if cleanPath == cleanTarget { + return true + } + + return isDir && strings.HasPrefix(cleanPath, cleanTarget+string(filepath.Separator)) +} + +func lspCloseAndDeleteFiles(ctx context.Context, lsps *csync.Map[string, *lsp.Client], filePath string, isDir bool) { for client := range lsps.Seq() { for uri := range client.OpenFiles() { path, err := protocol.DocumentURI(uri).Path() if err != nil { continue } - if path != cleanPath && !strings.HasPrefix(path, cleanPath+string(filepath.Separator)) { + if !shouldDeletePath(path, filePath, isDir) { continue } - _ = client.CloseFile(ctx, path) - _ = client.DidChangeWatchedFiles(ctx, protocol.DidChangeWatchedFilesParams{ - Changes: []protocol.FileEvent{ - { - URI: protocol.URIFromPath(path), - Type: protocol.Deleted, - }, - }, - }) + _ = client.DeleteFile(ctx, path) + } + } +} + +func deleteFileHistory(ctx context.Context, files history.Service, sessionID, filePath string, isDir bool) { + sessionFiles, err := files.ListLatestSessionFiles(ctx, sessionID) + if err != nil { + return + } + + for _, file := range sessionFiles { + if !shouldDeletePath(file.Path, filePath, isDir) { + continue + } + + fileEntry, err := files.GetByPathAndSession(ctx, file.Path, sessionID) + if err != nil { + continue } + _ = files.Delete(ctx, fileEntry.ID) } } diff --git a/internal/lsp/client.go b/internal/lsp/client.go index 79e879dee19f6c7406efdf9211e40f8562e4f5ee..aa3fa90c940fa2d70066abe0524943f11fcddc80 100644 --- a/internal/lsp/client.go +++ b/internal/lsp/client.go @@ -355,6 +355,21 @@ func (c *Client) CloseFile(ctx context.Context, filepath string) error { return nil } +// DeleteFile deletes a file from the LSP context. +func (c *Client) DeleteFile(ctx context.Context, filepath string) error { + if err := c.CloseFile(ctx, filepath); err != nil { + return err + } + return c.DidChangeWatchedFiles(ctx, protocol.DidChangeWatchedFilesParams{ + Changes: []protocol.FileEvent{ + { + URI: protocol.URIFromPath(filepath), + Type: protocol.Deleted, + }, + }, + }) +} + // OpenFiles returns an iterator over all currently open file URIs. func (c *Client) OpenFiles() iter.Seq[string] { return func(yield func(string) bool) { diff --git a/internal/tui/components/chat/sidebar/sidebar.go b/internal/tui/components/chat/sidebar/sidebar.go index 9b3d52dadb9a7677bdb5db4b3a8360e7385775ba..585e1754e3e515b58e3afa995a597e7d178ff79e 100644 --- a/internal/tui/components/chat/sidebar/sidebar.go +++ b/internal/tui/components/chat/sidebar/sidebar.go @@ -178,6 +178,11 @@ func (m *sidebarCmp) View() string { func (m *sidebarCmp) handleFileHistoryEvent(event pubsub.Event[history.File]) tea.Cmd { return func() tea.Msg { 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 {